Create React App から Next.js への移行方法

このガイドでは、既存の Create React App (CRA) サイトを Next.js に移行する方法を説明します。

移行する理由

Create React App から Next.js に移行する理由はいくつかあります:

初期ページ読み込みの遅さ

Create React App は純粋なクライアントサイドの React を使用しています。シングルページアプリケーション (SPA) として知られるクライアントサイドのみのアプリケーションは、初期ページの読み込みが遅くなる傾向があります。これには以下の理由があります:

  1. ブラウザは、React コードとアプリケーション全体のバンドルがダウンロードされて実行されるまで待機する必要があり、その後でデータ読み込みのリクエストを送信できます。
  2. 新しい機能や依存関係を追加するたびにアプリケーションコードが肥大化します。

自動コード分割の欠如

前述の読み込み遅延の問題は、コード分割である程度軽減できます。しかし、手動でコード分割を試みると、意図せずネットワークウォーターフォールを引き起こす可能性があります。Next.js にはルーターとビルドパイプラインに自動コード分割とツリーシェイキングが組み込まれています。

ネットワークウォーターフォール

パフォーマンス低下の一般的な原因は、アプリケーションがデータを取得するためにクライアント-サーバー間で順次リクエストを行う場合です。SPA でのデータ取得パターンの1つは、プレースホルダーをレンダリングし、コンポーネントがマウントされた後にデータを取得することです。残念ながら、子コンポーネントは親コンポーネントが自身のデータの読み込みを完了した後にのみデータ取得を開始できるため、リクエストの「ウォーターフォール」が発生します。

Next.js ではクライアントサイドのデータ取得もサポートされていますが、データ取得をサーバーに移行することも可能です。これによりクライアント-サーバー間のウォーターフォールを完全に排除できる場合があります。

高速で意図的なローディング状態

React Suspense を通じたストリーミングの組み込みサポートにより、ネットワークウォーターフォールを作成することなく、UIのどの部分をどの順序で最初に読み込むかを定義できます。

これにより、読み込みが速くレイアウトシフトのないページを構築できます。

データ取得戦略の選択

Next.js では、必要に応じてページまたはコンポーネントレベルでデータ取得戦略を選択できます。例えば、CMSからデータを取得し、ブログ投稿をビルド時にレンダリング(SSG)して高速に読み込むことも、必要に応じてリクエスト時にデータを取得(SSR)することも可能です。

ミドルウェア

Next.js ミドルウェアを使用すると、リクエストが完了する前にサーバー上でコードを実行できます。例えば、認証が必要なページではミドルウェアでユーザーをログインページにリダイレクトすることで、認証されていないコンテンツが一瞬表示されるのを防げます。A/Bテスト、実験、国際化などの機能にも使用できます。

組み込みの最適化

画像フォントサードパーティスクリプトは、アプリケーションのパフォーマンスに大きな影響を与えることがよくあります。Next.js にはこれらを自動的に最適化する専用コンポーネントとAPIが含まれています。

移行手順

私たちの目標は、できるだけ早く動作する Next.js アプリケーションを取得し、その後段階的に Next.js の機能を採用できるようにすることです。最初に、既存のルーターをすぐに置き換えることなく、アプリケーションを純粋なクライアントサイドアプリケーション (SPA) として扱います。これにより複雑さとマージコンフリクトを減らせます。

: カスタム homepage フィールドを package.json に設定している、カスタムサービスワーカーを使用している、または特定の Babel/webpack 調整を行っているなど、高度な CRA 構成を使用している場合は、このガイドの最後にある追加の考慮事項セクションを参照し、Next.js でこれらの機能を複製または適応するためのヒントを確認してください。

ステップ 1: Next.js 依存関係のインストール

既存のプロジェクトに Next.js をインストールします:

Terminal
npm install next@latest

ステップ 2: Next.js 設定ファイルの作成

プロジェクトのルート(package.json と同じレベル)に next.config.ts を作成します。このファイルには Next.js の設定オプションが含まれます。

next.config.ts
import type { NextConfig } from 'next'

const nextConfig: NextConfig = {
  output: 'export', // シングルページアプリケーション (SPA) として出力
  distDir: 'build', // ビルド出力ディレクトリを `build` に変更
}

export default nextConfig

: output: 'export' を使用すると静的エクスポートを行います。SSR や API などのサーバーサイド機能にはアクセスできません。この行を削除すると Next.js のサーバー機能を活用できます。

ステップ 3: ルートレイアウトの作成

Next.js の App Router アプリケーションには、すべてのページをラップする ルートレイアウト ファイル(React Server Component)を含める必要があります。

CRA アプリケーションでルートレイアウトファイルに最も近いのは public/index.html で、<html><head><body> タグが含まれています。

  1. src フォルダ内(またはプロジェクトルートに app を配置したい場合)に新しい app ディレクトリを作成します。
  2. app ディレクトリ内に layout.tsx(または layout.js)ファイルを作成します:
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> に置き換えます。

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>
  )
}

豆知識: Next.js はデフォルトで CRA の public/manifest.json、追加のアイコン、テスト設定 を無視します。これらが必要な場合、Next.js には Metadata APITesting 設定でサポートがあります。

ステップ 4: メタデータ

Next.js は自動的に <meta charset="UTF-8" /><meta name="viewport" content="width=device-width, initial-scale=1" /> タグを含めるため、<head> からこれらを削除できます:

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.icoicon.pngrobots.txt などの メタデータファイル は、app ディレクトリの最上位に配置されている限り、自動的にアプリケーションの <head> タグに追加されます。サポートされているすべてのファイルapp ディレクトリに移動した後、<link> タグを安全に削除できます:

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 オブジェクト に移動します:

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 にインポートします:

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 として維持し、すべてのルートをインターセプトしたいため、オプションのキャッチオールルート を使用します。

  1. app 内に [[...slug]] ディレクトリを作成します。
app
 [[...slug]]
 page.tsx
 layout.tsx
  1. page.tsx に以下を追加します:
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 のルート App コンポーネントを Client Component 内に埋め込み、すべてのロジックをクライアントサイドに維持します。Next.js を初めて使用する場合、クライアントコンポーネント(デフォルト)はサーバー上でプリレンダリングされることに注意してください。これらはクライアントサイドJavaScriptを実行する追加機能を持つものと考えることができます。

app/[[...slug]]/ 内に client.tsx(または client.js)を作成します:

'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' ディレクティブにより、このファイルはClient Componentになります。
  • dynamic インポートと ssr: false により、<App /> コンポーネントのサーバーサイドレンダリングが無効になり、真にクライアント専用(SPA)になります。

次に、page.tsx(または page.js)を更新して新しいコンポーネントを使用します:

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>widthheight属性を自動的に設定します。これにより、画像の読み込み時にレイアウトシフトが防止されます。ただし、片方の寸法のみがスタイリングされ、もう一方がautoにスタイリングされていない画像がアプリに含まれている場合、問題が発生する可能性があります。autoにスタイリングされていない場合、その寸法は<img>の寸法属性の値にデフォルト設定され、画像が歪んで表示される可能性があります。

<img>タグを維持することで、アプリケーションの変更量を減らし、上記の問題を防ぐことができます。その後、必要に応じてローダーの設定による画像最適化を利用するために<Image>コンポーネントに移行するか、自動画像最適化を備えたデフォルトのNext.jsサーバーに移行できます。

/publicからインポートされた画像の絶対インポートパスを相対インポートに変換:

// 変更前
import logo from '/logo.png'

// 変更後
import logo from '../public/logo.png'

<img>タグに画像オブジェクト全体ではなくsrcプロパティを渡す:

// 変更前
<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コマンドを使用するように更新します。また、.nextnext-env.d.ts.gitignoreに追加します:

package.json
{
  "scripts": {
    "dev": "next dev --turbopack",
    "build": "next build",
    "start": "npx serve@latest ./build"
  }
}
.gitignore
# ...
.next
next-env.d.ts

これで次のコマンドを実行できます:

npm run dev

http://localhost:3000を開きます。アプリケーションがNext.jsで(SPAモードで)動作しているのが確認できるはずです。

ステップ11: クリーンアップ

Create React Appに固有のアーティファクトを削除できます:

  • public/index.html
  • src/index.tsx
  • src/react-app-env.d.ts
  • reportWebVitalsの設定
  • react-scripts依存関係(package.jsonからアンインストール)

追加の考慮事項

CRAでのカスタムhomepageの使用

CRAのpackage.jsonhomepageフィールドを使用して特定のサブパスでアプリを提供していた場合、Next.jsではnext.config.tsbasePath設定を使用して再現できます:

next.config.ts
import { NextConfig } from 'next'

const nextConfig: NextConfig = {
  basePath: '/my-subpath',
  // ...
}

export default nextConfig

カスタムService Workerの扱い

CRAのサービスワーカー(例: create-react-appserviceWorker.js)を使用していた場合、Next.jsでプログレッシブウェブアプリケーション(PWA)を作成する方法を学べます。

APIリクエストのプロキシ

CRAアプリがpackage.jsonproxyフィールドを使用してバックエンドサーバーにリクエストを転送していた場合、next.config.tsNext.jsリライトで再現できます:

next.config.ts
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の設定を拡張できます:

next.config.ts
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を設定します。next-env.d.tstsconfig.jsoninclude配列にリストされていることを確認してください:

{
  "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機能を活用していませんが、これらを段階的に導入できます:

注記: 静的エクスポート(output: 'export')の使用は現在、useParamsフックや他のサーバーフィーチャをサポートしていません。すべてのNext.js機能を使用するには、next.config.tsからoutput: 'export'を削除してください。