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

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

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

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

データ取得戦略の選択

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

ミドルウェア

Next.js ミドルウェアを使用すると、リクエストが完了する前にサーバー上でコードを実行できます。例えば、認証が必要なページに対してミドルウェアでログインページにリダイレクトすることで、未認証コンテンツのフラッシュを防げます。また、A/B テスト、実験、国際化 (i18n) などの機能にも使用できます。

組み込みの最適化

画像フォントサードパーティスクリプトは、アプリケーションのパフォーマンスに大きな影響を与えることがよくあります。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 '...'
}

次に、古い 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>
  )
}

豆知識: 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> からこれらを削除できます:

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

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

最後に、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>
  )
}

上記の変更により、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 '...' // 後で更新します
}

これにより、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' ディレクティブにより、このファイルは 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 />
}

ステップ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>のdimension属性の値にデフォルト設定され、画像が歪んで表示される可能性があります。

<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.config.tsbasePath設定を使用してNext.jsで再現できます:

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'を削除してください。