Create React App から Next.js への移行方法
このガイドでは、既存の Create React App (CRA) サイトを Next.js に移行する方法を説明します。
なぜ移行するのか?
Create React App から Next.js に移行したい理由はいくつかあります:
初期ページ読み込み時間の遅さ
Create React App は純粋にクライアントサイドの React を使用しています。クライアントサイドのみのアプリケーション、別名 シングルページアプリケーション (SPA) は、初期ページ読み込み時間が遅くなることがよくあります。これは以下の理由によります:
- ブラウザは、React コードとアプリケーション全体のバンドルがダウンロードされて実行されるまで待つ必要があります。
- 新しい機能や依存関係を追加するたびにアプリケーションコードが増加します。
自動コード分割がない
前述の読み込み時間の遅さの問題は、コード分割である程度緩和できます。しかし、手動でコード分割を試みると、ネットワークウォーターフォールを引き起こす可能性があります。Next.js は、自動コード分割と tree-shaking をルーターとビルドパイプラインに組み込んでいます。
ネットワークウォーターフォール
パフォーマンスが悪化する一般的な原因は、データを取得するためにクライアントとサーバー間で順次リクエストを行うことです。SPA でのデータ取得のパターンの1つは、プレースホルダーをレンダリングし、コンポーネントがマウントされた後にデータを取得することです。残念ながら、子コンポーネントは親が自身のデータを読み込んだ後でしかデータを取得できないため、リクエストの「ウォーターフォール」が発生します。
Next.js ではクライアントサイドのデータ取得もサポートされていますが、データ取得をサーバーに移動することもできます。これにより、クライアントとサーバー間のウォーターフォールが完全に排除されることがよくあります。
高速で意図的な読み込み状態
React Suspense を通じたストリーミング の組み込みサポートにより、ネットワークウォーターフォールを作成せずに、UI のどの部分を最初にどの順序で読み込むかを定義できます。
これにより、ページの読み込みが速くなり、レイアウトシフトを排除できます。
データ取得戦略の選択
必要に応じて、Next.js ではページまたはコンポーネントレベルでデータ取得戦略を選択できます。たとえば、CMS からデータを取得し、ビルド時(SSG)にブログ投稿をレンダリングして高速な読み込み速度を実現したり、必要に応じてリクエスト時(SSR)にデータを取得したりできます。
ミドルウェア
Next.js ミドルウェア を使用すると、リクエストが完了する前にサーバー上でコードを実行できます。たとえば、認証が必要なページのミドルウェアでユーザーをログインページにリダイレクトすることで、未認証コンテンツのフラッシュを回避できます。また、A/B テスト、実験、国際化 などの機能にも使用できます。
組み込みの最適化
画像、フォント、およびサードパーティスクリプト は、アプリケーションのパフォーマンスに大きな影響を与えることがよくあります。Next.js には、これらを自動的に最適化するための専用コンポーネントと API が含まれています。
移行手順
私たちの目標は、できるだけ早く動作する Next.js アプリケーションを作成し、その後で Next.js の機能を段階的に採用できるようにすることです。まず、アプリケーションを純粋なクライアントサイドアプリケーション(SPA)として扱い、既存のルーターをすぐに置き換えないようにします。これにより、複雑さとマージの競合が軽減されます。
注意:
package.jsonのカスタムhomepageフィールド、カスタムサービスワーカー、特定の Babel/webpack 調整など、CRA の高度な設定を使用している場合は、Next.js でこれらの機能を再現または適応するためのヒントについて、このガイドの最後にある 追加の考慮事項 セクションを参照してください。
ステップ 1: Next.js の依存関係をインストールする
既存のプロジェクトに Next.js をインストールします:
npm install next@latest
ステップ 2: Next.js の設定ファイルを作成する
プロジェクトの root に next.config.ts を作成します(package.json と同じレベル)。このファイルには、Next.js の設定オプションが含まれます。
import type { NextConfig } from 'next'
const nextConfig: NextConfig = {
output: 'export', // シングルページアプリケーション (SPA) を出力します
distDir: 'build', // ビルド出力ディレクトリを `build` に変更します
}
export default nextConfig
注意:
output: 'export'を使用すると、静的エクスポートを行っていることを意味します。サーバーサイドの機能(SSR や API など)にはアクセスできません。Next.js のサーバー機能を活用するには、この行を削除できます。
ステップ 3: Root レイアウトを作成する
Next.js App Router アプリケーションには、すべてのページをラップする root レイアウト ファイルが必要です。これは、React Server Component です。
CRA アプリケーションでの root レイアウトファイルに最も近いものは、<html>、<head>、および <body> タグを含む public/index.html です。
srcディレクトリ内に新しいappディレクトリを作成します(または、appを root に配置する場合はプロジェクトの root に作成します)。appディレクトリ内にlayout.tsx(またはlayout.js)ファイルを作成します:
- TypeScript
- JavaScript
export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
return '...'
}
export default function RootLayout({ children }) {
return '...'
}
古い index.html の内容をこの <RootLayout> コンポーネントにコピーします。body div#root(および body noscript)を <div id="root">{children}</div> に置き換えます。
- TypeScript
- JavaScript
export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<html lang="en">
<head>
<meta charSet="UTF-8" />
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>React App</title>
<meta name="description" content="Web site created..." />
</head>
<body>
<div id="root">{children}</div>
</body>
</html>
)
}
export default function RootLayout({ children }) {
return (
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>React App</title>
<meta name="description" content="Web site created..." />
</head>
<body>
<div id="root">{children}</div>
</body>
</html>
)
}
Good to know: Next.js はデフォルトで CRA の
public/manifest.json、追加のアイコン、およびテスト設定を無視します。これらが必要な場合、Next.js は Metadata API とテストのセットアップをサポートしています。
ステップ 4: メタデータ
Next.js は <meta charset="UTF-8" /> と <meta name="viewport" content="width=device-width, initial-scale=1" /> タグを自動的に含めるため、<head> から削除できます:
- TypeScript
- JavaScript
export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<html lang="en">
<head>
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
<title>React App</title>
<meta name="description" content="Web site created..." />
</head>
<body>
<div id="root">{children}</div>
</body>
</html>
)
}
export default function RootLayout({ children }) {
return (
<html lang="en">
<head>
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
<title>React App</title>
<meta name="description" content="Web site created..." />
</head>
<body>
<div id="root">{children}</div>
</body>
</html>
)
}
favicon.ico、icon.png、robots.txt などのメタデータファイルは、app ディレクトリのトップレベルに配置されている限り、アプリケーションの <head> タグに自動的に追加されます。すべてのサポートされているファイルを app ディレクトリに移動した後、それらの <link> タグを安全に削除できます:
- TypeScript
- JavaScript
export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<html lang="en">
<head>
<title>React App</title>
<meta name="description" content="Web site created..." />
</head>
<body>
<div id="root">{children}</div>
</body>
</html>
)
}
export default function RootLayout({ children }) {
return (
<html lang="en">
<head>
<title>React App</title>
<meta name="description" content="Web site created..." />
</head>
<body>
<div id="root">{children}</div>
</body>
</html>
)
}
最後に、Next.js は Metadata API を使用して最後の <head> タグを管理できます。最終的なメタデータ情報をエクスポートされた metadata オブジェクト に移動します:
- TypeScript
- JavaScript
import type { Metadata } from 'next'
export const metadata: Metadata = {
title: 'React App',
description: 'Web site created with Next.js.',
}
export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<html lang="en">
<body>
<div id="root">{children}</div>
</body>
</html>
)
}
export const metadata = {
title: 'React App',
description: 'Web site created with Next.js.',
}
export default function RootLayout({ children }) {
return (
<html lang="en">
<body>
<div id="root">{children}</div>
</body>
</html>
)
}
上記の変更により、index.html にすべてを宣言する方法から、フレームワークに組み込まれた Next.js の規約ベースのアプローチ(Metadata API)を使用する方法に移行しました。このアプローチにより、ページの SEO とウェブの共有性をより簡単に向上させることができます。
ステップ 5: スタイル
CRA と同様に、Next.js は CSS Modules を標準でサポートしています。また、グローバル CSS インポートもサポートしています。
グローバル CSS ファイルがある場合は、app/layout.tsx にインポートします:
- TypeScript
import '../index.css'
export const metadata = {
title: 'React App',
description: 'Web site created with Next.js.',
}
export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<html lang="en">
<body>
<div id="root">{children}</div>
</body>
</html>
)
}
Tailwind CSS を使用している場合は、インストールドキュメントを参照してください。
ステップ 6: エントリーポイントページを作成する
Create React App は src/index.tsx(または index.js)をエントリーポイントとして使用します。Next.js(App Router)では、app ディレクトリ内の各フォルダがルートに対応し、各フォルダには page.tsx が必要です。
現在のところアプリを SPA として保持し、すべてのルートをインターセプトしたいので、optional catch-all route を使用します。
app内に[[...slug]]ディレクトリを作成します。
app
┣ [[...slug]]
┃ ┗ page.tsx
┣ layout.tsx
page.tsxに次の内容を追加します:
- TypeScript
- JavaScript
export function generateStaticParams() {
return [{ slug: [''] }]
}
export default function Page() {
return '...' // 後で更新します
}
export function generateStaticParams() {
return [{ slug: [''] }]
}
export default function Page() {
return '...' // 後で更新します
}
これは Next.js に空のスラッグ(/)のための単一のルートを生成するよう指示し、すべてのルートを同じページにマッピングします。このページはServer Componentであり、静的 HTML にプリレンダリングされます。
ステップ 7: クライアント専用のエントリーポイントを追加する
次に、CRA の root App コンポーネントをClient Component内に埋め込み、すべてのロジックをクライアントサイドに残します。Next.js を初めて使用する場合、クライアントコンポーネント(デフォルトでは)もサーバーでプリレンダリングされることを知っておく価値があります。クライアントサイドの JavaScript を実行する追加の機能を持っていると考えることができます。
app/[[...slug]]/ に client.tsx(または client.js)を作成します:
- TypeScript
- JavaScript
'use client'
import dynamic from 'next/dynamic'
const App = dynamic(() => import('../../App'), { ssr: false })
export function ClientOnly() {
return <App />
}
'use client'
import dynamic from 'next/dynamic'
const App = dynamic(() => import('../../App'), { ssr: false })
export function ClientOnly() {
return <App />
}
'use client'ディレクティブは、このファイルをクライアントコンポーネントにします。dynamicインポートとssr: falseは<App />コンポーネントのサーバーサイドレンダリングを無効にし、真にクライアント専用(SPA)にします。
次に、page.tsx(または page.js)を更新して新しいコンポーネントを使用します:
- TypeScript
- JavaScript
import { ClientOnly } from './client'
export function generateStaticParams() {
return [{ slug: [''] }]
}
export default function Page() {
return <ClientOnly />
}
import { ClientOnly } from './client'
export function generateStaticParams() {
return [{ slug: [''] }]
}
export default function Page() {
return <ClientOnly />
}
ステップ 8: 静的画像インポートを更新する
CRA では、画像ファイルをインポートすると、その公開 URL が文字列として返されます:
import image from './img.png'
export default function App() {
return <img src={image} />
}
Next.js では、静的画像インポートはオブジェクトを返します。このオブジェクトは Next.js の <Image> コンポーネントで直接使用することができ、または既存の <img> タグでオブジェクトの src プロパティを使用することができます。
<Image> コンポーネントには自動画像最適化の追加の利点があります。<Image> コンポーネントは、画像の寸法に基づいて結果の <img> の width と height 属性を自動的に設定します。これにより、画像が読み込まれるときのレイアウトシフトを防ぎます。ただし、アプリに寸法の一方のみがスタイルされ、他方が auto にスタイルされていない画像が含まれている場合、問題が発生する可能性があります。auto にスタイルされていない場合、寸法は <img> の寸法属性の値にデフォルト設定され、画像が歪んで表示される可能性があります。
<img> タグを保持することで、アプリケーションの変更量を減らし、上記の問題を防ぐことができます。その後、ローダーを設定するか、画像を自動的に最適化するデフォルトの Next.js サーバーに移行することで、画像を最適化するために <Image> コンポーネントに移行することができます。
/public からインポートされた画像の絶対インポートパスを相対インポートに変換します:
// 変更前
import logo from '/logo.png'
// 変更後
import logo from '../public/logo.png'
画像オブジェクト全体ではなく、画像の src プロパティを <img> タグに渡します:
// 変更前
<img src={logo} />
// 変更後
<img src={logo.src} />
または、ファイル名に基づいて画像アセットの公開 URL を参照することもできます。たとえば、public/logo.png はアプリケーションの /logo.png で画像を提供し、これが src 値になります。
警告: TypeScript を使用している場合、
srcプロパティにアクセスするときに型エラーが発生する可能性があります。これを修正するには、tsconfig.jsonファイルのinclude配列にnext-env.d.tsを追加する必要があります。Next.js は、ステップ 9 でアプリケーションを実行するときにこのファイルを自動的に生成します。
ステップ 9: 環境変数を移行する
Next.js は CRA と同様に環境変数をサポートしていますが、ブラウザで公開したい変数には NEXT_PUBLIC_ プレフィックスが必要です。
主な違いは、クライアントサイドで環境変数を公開するために使用されるプレフィックスです。REACT_APP_ プレフィックスを持つすべての環境変数を NEXT_PUBLIC_ に変更します。
ステップ 10: package.json のスクリプトを更新する
package.json のスクリプトを Next.js コマンドに更新します。また、.next と next-env.d.ts を .gitignore に追加します:
{
"scripts": {
"dev": "next dev --turbopack",
"build": "next build",
"start": "npx serve@latest ./build"
}
}
# ... \{#}
.next
next-env.d.ts
次のコマンドを実行できます:
npm run dev
http://localhost:3000 を開きます。Next.js(SPA モード)でアプリケーションが実行されているのが確認できるはずです。
ステップ 11: クリーンアップ
Create React App に特有のアーティファクトを削除できます:
public/index.htmlsrc/index.tsxsrc/react-app-env.d.tsreportWebVitalsのセットアップreact-scriptsの依存関係(package.jsonからアンインストール)
追加の考慮事項
CRA でカスタム homepage を使用する
CRA の package.json で homepage フィールドを使用してアプリを特定のサブパスで提供していた場合、Next.js の next.config.ts で basePath 設定を使用してそれを再現できます:
import { NextConfig } from 'next'
const nextConfig: NextConfig = {
basePath: '/my-subpath',
// ...
}
export default nextConfig
カスタム Service Worker の処理
CRA のサービスワーカー(例:create-react-app の serviceWorker.js)を使用していた場合、Next.js でプログレッシブウェブアプリケーション (PWA)を作成する方法を学ぶことができます。
API リクエストのプロキシ
CRA アプリで package.json の proxy フィールドを使用してバックエンドサーバーへのリクエストを転送していた場合、next.config.ts で Next.js のリライトを使用してこれを再現できます:
import { NextConfig } from 'next'
const nextConfig: NextConfig = {
async rewrites() {
return [
{
source: '/api/:path*',
destination: 'https://your-backend.com/:path*',
},
]
},
}
カスタム Webpack / Babel 設定
CRA でカスタム webpack または Babel 設定を持っていた場合、next.config.ts で Next.js の設定を拡張できます:
import { NextConfig } from 'next'
const nextConfig: NextConfig = {
webpack: (config, { isServer }) => {
// ここで webpack 設定を変更します
return config
},
}
export default nextConfig
注意: これには
devスクリプトから--turbopackを削除して Turbopack を無効にする必要があります。
TypeScript のセットアップ
Next.js は tsconfig.json がある場合、自動的に TypeScript をセットアップします。tsconfig.json の include 配列に next-env.d.ts がリストされていることを確認してください:
{
"include": ["next-env.d.ts", "app/**/*", "src/**/*"]
}
バンドラーの互換性
Create React App と Next.js はどちらもデフォルトで webpack をバンドリングに使用します。Next.js はまた、ローカル開発をより高速にするための Turbopack を提供しています:
next dev --turbopack
CRA から高度な webpack 設定を移行する必要がある場合は、カスタム webpack 設定を提供することもできます。
次のステップ
すべてが正常に動作した場合、現在はシングルページアプリケーションとして動作する Next.js アプリケーションがあります。まだサーバーサイドレンダリングやファイルベースのルーティングなどの Next.js の機能を活用していませんが、これらを段階的に行うことができます:
- Next.js App Router にReact Router から移行して:
<Image>コンポーネントで画像を最適化next/fontでフォントを最適化<Script>コンポーネントでサードパーティスクリプトを最適化npx next lintを実行し、プロジェクトのニーズに合わせて設定することで、Next.js 推奨ルールでESLint を有効化
注意: 静的エクスポート(
output: 'export')を使用すると、useParamsフックやその他のサーバー機能が現在サポートされていません。すべての Next.js 機能を使用するには、next.config.tsからoutput: 'export'を削除してください。