Menu

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におけるデータ取得の一般的なパターンは、プレースホルダーをレンダリングし、コンポーネントがマウントされた後にデータを取得することです。残念ながら、子コンポーネントは親が自身のデータ読み込みを完了した後でしかデータの取得を開始できないため、リクエストの「ウォーターフォール」が発生します。

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ルーターアプリケーションには、すべてのページをラップするReactサーバーコンポーネントであるルートレイアウトファイルが必要です。

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

  1. srcディレクトリ内に新しいappディレクトリを作成します(または、ルートにappを配置したい場合はプロジェクトルートに作成します)。
  2. appディレクトリ内にlayout.tsx(またはlayout.js)ファイルを作成します:
app/layout.tsx
TypeScript
export default function RootLayout({
  children,
}: {
  children: React.ReactNode
}) {
  return '...'
}

次に、古いindex.htmlの内容をこの<RootLayout>コンポーネントにコピーします。body div#root(およびbody noscript)を<div id="root">{children}</div>に置き換えてください。

app/layout.tsx
TypeScript
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はメタデータAPIテストのセットアップをサポートしています。

ステップ4:メタデータ

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

app/layout.tsx
TypeScript
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>タグは安全に削除できます:

app/layout.tsx
TypeScript
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はメタデータAPIを使用して残りの<head>タグを管理できます。最終的なメタデータ情報をエクスポートされたmetadataオブジェクトに移動します:

app/layout.tsx
TypeScript
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の規約ベースのアプローチ(メタデータAPI)に移行しました。このアプローチにより、ページのSEOとウェブ共有性をより簡単に改善できます。

ステップ5:スタイル

CRAと同様に、Next.jsはデフォルトでCSSモジュールをサポートしています。また、グローバルCSSインポートもサポートしています。

グローバルCSSファイルがある場合は、app/layout.tsxにインポートしてください:

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ルーター)では、appディレクトリ内の各フォルダがルートに対応し、各フォルダにはpage.tsxが必要です。

アプリをSPAとして維持し、すべてのルートをインターセプトするために、オプションのキャッチオールルートを使用します。

  1. app内に[[...slug]]ディレクトリを作成します。
app
 [[...slug]]
 page.tsx
 layout.tsx
  1. page.tsxに以下を追加します
app/[[...slug]]/page.tsx
TypeScript
export function generateStaticParams() {
  return [{ slug: [''] }]
}
 
export default function Page() {
  return '...' // この部分は後で更新します
}

これにより、Next.jsは空のスラグ(/)に対して単一のルートを生成し、実質的にすべてのルートを同じページにマッピングします。このページは静的HTMLにプリレンダリングされるサーバーコンポーネントです。

ステップ7:クライアントのみのエントリポイントを追加する

次に、CRAのルートAppコンポーネントをクライアントコンポーネント内に埋め込み、すべてのロジックをクライアントサイドに維持します。Next.jsを初めて使用する場合、クライアントコンポーネントは(デフォルトでは)依然としてサーバー上でプリレンダリングされることを知っておくと良いでしょう。クライアントサイドJavaScriptを実行する追加機能を持っていると考えることができます。

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

app/[[...slug]]/client.tsx
TypeScript
'use client'
 
import dynamic from 'next/dynamic'
 
const App = dynamic(() => import('../../App'), { ssr: false })
 
export function ClientOnly() {
  return <App />
}
  • 'use client'ディレクティブによって、このファイルはクライアントコンポーネントになります。
  • ssr: falseを指定したdynamicインポートは、<App />コンポーネントのサーバーサイドレンダリングを無効にし、真にクライアントのみ(SPA)にします。

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

app/[[...slug]]/page.tsx
TypeScript
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>コンポーネントで直接使用するか、オブジェクトのsrcプロパティを既存の<img>タグで使用できます。

<Image>コンポーネントには自動画像最適化という追加の利点があります。<Image>コンポーネントは画像の寸法に基づいて結果の<img>widthheight属性を自動的に設定します。これにより、画像の読み込み時のレイアウトシフトを防ぎます。ただし、もう一方のディメンションがautoにスタイル設定されていない状態で、ディメンションの1つだけがスタイル設定されている画像がアプリに含まれている場合、問題が発生する可能性があります。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コマンドを使用するように更新します。また、.gitignore.nextnext-env.d.tsを追加してください:

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のセットアップ

tsconfig.jsonがある場合、Next.jsは自動的にTypeScriptをセットアップします。tsconfig.jsoninclude配列に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機能はまだ活用していませんが、今後段階的に導入することができます:

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