TypeScriptを使用したNext.js 5からNext.js 6へ移行した時行った対応について

Next.jsReactReduxTypeScript
TypeScriptを使用したNext.js 5からNext.js 6へ移行した時行った対応について
Article

Next.jsはこのサイトでも使っているのですが
比較的容易にBFF(バックエンドフォーフロントエンド)が実現できるツールとして大変有用です。

UXを実現していくためにSPA(シングルーページアプリケーション)という
アーキテクトがあげられますが

単純なSPAとしての問題点であげられるのが

  • リクエストの複雑化によるランドトリップ
  • 初回ロード時間の遅延

このあたりの課題の解決としてBFFというアーキテクチャの設計思想があります。

そのためこれらの解決を実現していくために
このサイトではNext.js を採用しました。

そんなNext.js6が5月にリリースされたため
マイグレーションのログとして内容をまとめました。

環境

  • React 16.3.2
  • Next.js 5.1
  • typescript 2.8.3
terminal
$ yarn upgrade --latest next next-redux-wrapper typescript
terminal
$ yarn upgrade --latest @zeit/next-typescript @types/next

packageのバージョン

next-redux-wrapper関連

アップデート後に出たエラー

terminal
__WEBPACK_IMPORTED_MODULE_2_next_redux_wrapper__ is not a function

解決方法

書き方が変わりました。

next-redux-wrapper1系

getInitialPropsを呼び出したReactクラスをwithRedux でラップすることで
ステートを子コンポーネントで参照できます。

/pages/index.tsx
import * as React from 'React'; import { createStore } from 'redux'; import * as withRedux from 'next-redux-wrapper'; import Page from './components/page'; const reducer = (state = {hoge: ''}, action) => { switch (action.type) { case 'HOGE': return {...state, hoge: action.payload}; default: return state } }; const makeStore = (initialState, options) => { return createStore(reducer, initialState); }; class App extends React.Component<object> { public static getInitialProps({ store }: Context): void{ const state = store.getState(); await store.dispatch({type: 'Hoge', payload: 'hoge'}); } public render(): JSX.Element { return ( <Page /> ); } } export default withRedux(makeStore)(App);

受け取った propsを出力

/components/page.tsx
import * as React from 'React'; const Page = (props) => { return <div>Prop from Redux {props.hoge}</div> } export default connect()(Page);

next-redux-wrapper2系

getInitialPropsを呼び出しreact-reduxに渡しProviderします
そのReactクラスをwithRedux でラップすることで
ステートを子コンポーネントで参照できます。

/pages/_app.tsx
import * as React from 'react'; import { createStore } from 'redux'; import { Provider } from 'react-redux'; import App, { Container } from 'next/app'; import withRedux from 'next-redux-wrapper'; const reducer = (state = { foo: '' }, action) => { switch (action.type) { case 'FOO': return { ...state, foo: action.payload }; default: return state } }; const makeStore = (initialState, options) => { return createStore(reducer, initialState); }; class MyApp extends App<object> { static getInitialProps({ Component, ctx }) { const pageProps = Component.getInitialProps ? await Component.getInitialProps(ctx) : {}; return { pageProps }; } render() { const { Component, pageProps, store } = this.props; return ( <Container> <Provider store={store}> <Component {...pageProps} /> </Provider> </Container> ); } } export default withRedux(makeStore)(MyApp);

単純に withRedux の箇所を _app.tsx に切り出す形になりましたが
pagesがかなりシンプルになりました

各ページごとにストアへ保存するデータを分けてかくケースについては
以前よりかなりスッキリ見やすくなっています。

/components/page.tsx
import * as React from 'React'; import Page from './components/page'; class MyApp extends React.Component<object> { public static getInitialProps({ store }: Context):void{ const state = store.getState(); store.dispatch({type: 'Hoge', payload: 'hoge'}); } public render(): JSX.Element { return ( <Page /> ); } } export default App;

コネクト関数については以前と同じ形で使用できます。

/pages/index.tsx
import * as React from 'React'; const Page = (props: object) => { return <div>Prop from Redux {props.hoge}</div> } export default connect()(Page);

@zeit/next-typescript関連

アップデート後に出たエラー

typescriptLoaderOptionsはサポートされなくなりました

terminal
Error: `typescriptLoaderOptions` in next.config.js is no longer supported. https://err.sh/next-plugins/typescript-loader-options

解決方法

以下を削除

next.config.js
typescriptLoaderOptions: { transpileOnly: false }

'next / babel'モジュールを見つけることができません

terminal
UnhandledPromiseRejectionWarning: Error: Cannot find module 'next/babel' from '/Users/XXXX/XXXX'

解決方法

.babelrcを作成し下記を記述

babelrc
{ "presets": [ "next/babel", "@zeit/next-typescript/babel" ] }