Menu

Create React App からの移行

このガイドは、既存の Create React App サイトを 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からデータを取得してブログ投稿をビルド時にレンダリングし、CDNで効率的にキャッシュできます。

ミドルウェア

Next.js ミドルウェアを使用すると、リクエストが完了する前にサーバー上でコードを実行できます。これは、認証が必要なページにユーザーがアクセスした際に、ログインページにリダイレクトすることで、認証されていないコンテンツの一瞬のちらつきを避けるのに特に役立ちます。ミドルウェアは、実験や国際化にも役立ちます。

組み込みの最適化

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

移行手順

この移行の目的は、できるだけ早く動作する Next.js アプリケーションを作成し、その後 Next.js の機能を段階的に採用することです。最初は、既存のルーターを移行せずに、純粋なクライアントサイドアプリケーション(SPA)として保持します。これにより、移行プロセス中の問題の可能性を最小限に抑え、マージコンフリクトを減らすことができます。

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

最初に、next を依存関係としてインストールする必要があります:

Terminal
npm install next@latest

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

プロジェクトのルートに next.config.mjs を作成します。このファイルには Next.js 設定オプションが保持されます。

next.config.mjs
/** @type {import('next').NextConfig} */
const nextConfig = {
  output: 'export', // シングルページアプリケーション(SPA)を出力します。
  distDir: './build', // ビルド出力ディレクトリを `./dist` に変更します。
}
 
export default nextConfig

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

Next.js の App Router アプリケーションには、アプリケーション内のすべてのページをラップする ルートレイアウトファイルを含める必要があります。これは React サーバーコンポーネントです。

CRA アプリケーションで最も近いルートレイアウトファイルは、<html><head><body> タグを含む index.html ファイルです。

このステップでは、index.html ファイルをルートレイアウトファイルに変換します:

  1. src ディレクトリに新しい app ディレクトリを作成します。
  2. その app ディレクトリ内に新しい layout.tsx ファイルを作成します:
app/layout.tsx
TypeScript
export default function RootLayout({
  children,
}: {
  children: React.ReactNode
}) {
  return '...'
}

補足: レイアウトファイルには .js.jsx、または .tsx 拡張子を使用できます。

以前に作成した <RootLayout> コンポーネントに index.html ファイルの内容をコピーし、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 ファイル、追加のアイコン(faviconiconapple-icon 以外)、テスト設定を無視しますが、これらが必要な場合、Next.js もこれらのオプションをサポートしています。詳細は メタデータ APIテストのドキュメントを参照してください。

ステップ 4: メタデータ

Next.js にはデフォルトで meta charsetmeta viewport タグが含まれているため、<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はMetadata 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のフレームワークに組み込まれている規約ベースのアプローチ(Metadata API)に移行しました。このアプローチにより、SEOとWebページの共有性をより簡単に改善できます。

ステップ 5: スタイル

Create React Appと同様に、Next.jsはCSS Modulesを組み込みでサポートしています。

グローバルCSSファイルを使用している場合は、app/layout.tsx ファイルにインポートします:

app/layout.tsx
TypeScript
import '../index.css'
 
// ...

Tailwindを使用している場合、postcssautoprefixerをインストールする必要があります:

Terminal
npm install postcss autoprefixer

次に、プロジェクトのルートに postcss.config.js ファイルを作成します:

postcss.config.js
module.exports = {
  plugins: {
    tailwindcss: {},
    autoprefixer: {},
  },
}

ステップ 6: エントリーポイントページの作成

Next.jsでは、page.tsx ファイルを作成することでアプリケーションのエントリーポイントを宣言します。このファイルはCreate React Appの src/index.tsx ファイルに最も近いものです。このステップでは、アプリケーションのエントリーポイントを設定します。

app ディレクトリに [[...slug]] ディレクトリを作成します。

このガイドではNext.jsをSPA(シングルページアプリケーション)として最初に設定することを目指すため、アプリケーションの可能なすべてのルートをキャッチするページエントリーポイントが必要です。そのため、app ディレクトリに新しい [[...slug]] ディレクトリを作成します。

このディレクトリはオプションのキャッチオールルートセグメントと呼ばれます。Next.jsはルートを定義するためにディレクトリを使用するファイルシステムベースのルーターを使用します。この特殊なディレクトリにより、アプリケーションのすべてのルートがその中の page.tsx ファイルに転送されます。

app/[[...slug]] ディレクトリ内に次の内容の新しい page.tsx ファイルを作成します:

app/[[...slug]]/page.tsx
TypeScript
export function generateStaticParams() {
  return [{ slug: [''] }]
}
 
export default function Page() {
  return '...' // これは後で更新します
}

このファイルはサーバーコンポーネントです。next buildを実行すると、ファイルは静的アセットにプリレンダリングされます。動的コードは_不要_です。

このファイルはグローバルCSSをインポートし、generateStaticParamsに、インデックスルート / のみを生成することを伝えます。

次に、クライアント専用で実行されるCreate React Appの残りの部分を移動します。

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' ディレクティブで定義されたクライアントコンポーネントです。クライアントコンポーネントは、クライアントに送信される前に、サーバー上でHTMLにプリレンダリングされます

クライアント専用のアプリケーションを開始するために、App コンポーネントからプリレンダリングを無効にするようNext.jsを構成できます。

const App = dynamic(() => import('../../App'), { ssr: false })

次に、エントリーポイントページを新しいコンポーネントを使用するように更新します:

app/[[...slug]]/page.tsx
TypeScript
import { ClientOnly } from './client'
 
export function generateStaticParams() {
  return [{ slug: [''] }]
}
 
export default function Page() {
  return <ClientOnly />
}

ステップ 7: 静的画像インポートの更新

Next.jsは静的画像インポートをCreate React Appとは少し異なる方法で処理します。Create React Appでは、画像ファイルをインポートするとその公開URLを文字列として返します:

App.tsx
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'

<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 でアプリケーションを実行すると、このファイルを自動的に生成します。

ステップ 8: 環境変数の移行

Next.js は、CRA と同様に .env 環境変数をサポートしています。

クライアント側で環境変数を公開するために使用される接頭辞が主な違いです。REACT_APP_ 接頭辞のある環境変数をすべて NEXT_PUBLIC_ に変更してください。

ステップ 9: package.json 内のスクリプトを更新

Next.js への移行が成功したかをテストするために、アプリケーションを実行できるはずです。ただし、その前に、package.json 内の scripts を Next.js 関連のコマンドで更新し、.nextnext-env.d.ts.gitignore ファイルに追加する必要があります:

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

npm run dev を実行し、http://localhost:3000 を開きます。Next.js でアプリケーションが実行されているはずです。

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

Create React App 関連の成果物をコードベースからクリーンアップできます:

  • public/index.html を削除
  • src/index.tsx を削除
  • src/react-app-env.d.ts を削除
  • reportWebVitals のセットアップを削除
  • CRA の依存関係(react-scripts)をアンインストール

バンドラーの互換性

Create React App と Next.js は、どちらもデフォルトで webpack を使用してバンドルします。

CRA アプリケーションを Next.js に移行する際、移行したいカスタム webpack 設定がある可能性があります。Next.js は、カスタム webpack 設定の提供をサポートしています。

さらに、Next.js は next dev --turbopack を使用して、ローカル開発のパフォーマンスを向上させる Turbopack をサポートしています。Turbopack は、互換性と段階的な導入のために、いくつかの webpack ローダーもサポートしています。

次のステップ

すべてが計画通りに進めば、シングルページアプリケーションとして動作する Next.js アプリケーションが完成しています。ただし、まだ Next.js の利点のほとんどを活用できていませんが、現時点から段階的な変更を加えて、すべての利点を得ることができます。次に行うことは以下のようなものです:

補足: 静的エクスポートは、現在 useParams フックの使用をサポートしていません