TypeScriptを使用したNext.js 5からNext.js 6へ移行した時行った対応について
この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。
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@5.1 → next@6.1.1
- next-redux-wrapper@1.3.7 → next-redux-wrapper@2.0.0-beta.6
- typescript@2.8.3 → typescript@2.9.2
- @zeit/next-typescript@0.1.1 → @zeit/next-typescript@1.1.0
- @types/next@2.4.8 → @types/next@6.0.3
next-redux-wrapper関連
アップデート後に出たエラー
terminal__WEBPACK_IMPORTED_MODULE_2_next_redux_wrapper__ is not a function
解決方法
書き方が変わりました。
next-redux-wrapper1系
getInitialPropsを呼び出したReactクラスをwithRedux でラップすることで
ステートを子コンポーネントで参照できます。
./pages/index.tsx
typescriptimport * 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.tsximport * 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.tsximport * 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.tsximport * as React from 'React';
const Page = (props: object) => {
return <div>Prop from Redux {props.hoge}</div>
}
export default connect()(Page);
@zeit/next-typescript関連
アップデート後に出たエラー
typescriptLoaderOptionsはサポートされなくなりました
terminalError: `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'モジュールを見つけることができません
terminalUnhandledPromiseRejectionWarning: Error: Cannot find module 'next/babel' from '/Users/XXXX/XXXX'
解決方法
.babelrcを作成し下記を記述
.babelrc{
"presets": [
"next/babel",
"@zeit/next-typescript/babel"
]
}
articleNext.js Edge Runtime vs Node Runtime:TTFB・コールドスタート・コストを実測検証
articleNext.js の Route Handlers で multipart/form-data が受け取れない問題の切り分け術
articleNext.js Server Components 時代のデータ取得戦略:fetch キャッシュと再検証の新常識
articleNext.js の 観測可能性入門:OpenTelemetry/Sentry/Vercel Analytics 連携
articleNext.js でドキュメントポータル:MDX/全文検索/バージョン切替の設計例
articleNext.js でインフィニットスクロールを実装:Route Handlers +`use` で滑らかデータ読込
articleTypeScript Project References 入門:大規模 Monorepo で高速ビルドを実現する設定手順
articleTypeScript Null 安全戦略の比較検証:ts-reset vs strictNullChecks vs noUncheckedIndexedAccess
articleESM/CJS 地獄から脱出!「ERR_REQUIRE_ESM」「import 文が使えない」を TypeScript で直す
articleTypeScript 型安全なフィーチャーフラグ設計:判別可能共用体で運用事故を防ぐ
articleTypeScript satisfies 演算子の実力:型の過剰/不足を一発検知する実践ガイド
articlePlaywright × TypeScript 超入門チュートリアル:型安全 E2E を最短構築
articleClips AI とは?自動切り抜き・自動字幕で動画編集を爆速化する仕組み
articleDeno とは?Node.js との違い・強み・ユースケースを最新整理
articlegpt-oss アーキテクチャを分解図で理解する:推論ランタイム・トークナイザ・サービング層の役割
articlePHP で社内業務自動化:CSV→DB 取込・定期バッチ・Slack 通知の実例
articleGPT-5 × Cloudflare Workers/Edge:低遅延サーバーレスのスターターガイド
articleNotebookLM と Notion AI/ChatGPT の比較:根拠提示とソース管理の違い
blogiPhone 17シリーズの発表!全モデルiPhone 16から進化したポイントを見やすく整理
blogGoogleストアから訂正案内!Pixel 10ポイント有効期限「1年」表示は誤りだった
blog【2025年8月】Googleストア「ストアポイント」は1年表記はミス?2年ルールとの整合性を検証
blogGoogleストアの注文キャンセルはなぜ起きる?Pixel 10購入前に知るべき注意点
blogPixcel 10シリーズの発表!全モデル Pixcel 9 から進化したポイントを見やすく整理
blogフロントエンドエンジニアの成長戦略:コーチングで最速スキルアップする方法
review今の自分に満足していますか?『持たざる者の逆襲 まだ何者でもない君へ』溝口勇児
reviewついに語られた業界の裏側!『フジテレビの正体』堀江貴文が描くテレビ局の本当の姿
review愛する勇気を持てば人生が変わる!『幸せになる勇気』岸見一郎・古賀史健のアドラー実践編で真の幸福を手に入れる
review週末を変えれば年収も変わる!『世界の一流は「休日」に何をしているのか』越川慎司の一流週末メソッド
review新しい自分に会いに行こう!『自分の変え方』村岡大樹の認知科学コーチングで人生リセット
review科学革命から AI 時代へ!『サピエンス全史 下巻』ユヴァル・ノア・ハラリが予見する人類の未来