メインコンテンツまでスキップ

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: Root レイアウトを作成する

Next.jsのApp Routerアプリケーションには、アプリケーション内のすべてのページをラップするReact Server ComponentであるRoot レイアウトファイルが含まれている必要があります。このファイルはappディレクトリの最上位に定義されます。

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

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

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

Good to know: Layoutファイルには、.js.jsx、または.tsxの拡張子を使用できます。

以前に作成した<RootLayout>コンポーネントにindex.htmlファイルの内容をコピーし、body.div#rootbody.noscriptのタグを<div id="root">{children}</div>に置き換えます:

app/layout.tsx
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>
)
}

Good to know: Next.jsはCRAのpublic/manifest.jsonファイル、追加のアイコン(faviconiconapple-iconを除く)、およびテスト設定を無視しますが、これらが必要な場合でもNext.jsはこれらのオプションをサポートしています。詳しくはMetadata APIおよびTestingのドキュメントをご覧ください。

ステップ4: メタデータ

Next.jsは既にデフォルトでmeta charsetおよびmeta viewportタグを含んでいるため、これらを<head>から安全に削除できます:

app/layout.tsx
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
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
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: スタイル

Create React Appと同様に、Next.jsにはCSSモジュールのサポートが組み込まれています。

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

app/layout.tsx
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ファイルを作成することでアプリケーションのエントリーポイントを宣言します。CRAでのこのファイルの最も近い同等物はsrc/index.tsxファイルです。このステップではアプリケーションのエントリーポイントを設定します。

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

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

このディレクトリは任意のキャッチオールルートセグメントと呼ばれます。Next.jsはディレクトリをルート定義に使用するファイルシステムに基づくルーターを使用しており、この特別なディレクトリはアプリケーションのすべてのルートがその中に含まれるpage.tsxファイルに導かれることを確実にします。

app/[[...slug]]ディレクトリ内に新しいpage.tsxファイルを作成し、次のコンテンツを追加します:

app/[[...slug]]/page.tsx
export function generateStaticParams() {
return [{ slug: [''] }]
}

export default function Page() {
return '...' // 更新します
}

このファイルはServer Componentです。next buildを実行すると、このファイルは静的なアセットにプリレンダリングされ、動的なコードを必要としません。

このファイルは、グローバルCSSをインポートし、generateStaticParamsに、生成するルートが1つだけであること、すなわち/でのインデックスルートであることを知らせます。

次に、クライアント専用で動作するCRAアプリケーションの残りを移動します。

app/[[...slug]]/client.tsx
'use client'

import dynamic from 'next/dynamic'

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

export function ClientOnly() {
return <App />
}

このファイルは、 'use client'ディレクティブによって定義されたClient Componentです。クライアントコンポーネントは、クライアントに送信される前にサーバー上でまだHTMLにプリレンダリングされます。

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

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

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

app/[[...slug]]/page.tsx
import { ClientOnly } from './client'

export function generateStaticParams() {
return [{ slug: [''] }]
}

export default function Page() {
return <ClientOnly />
}

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

Next.jsはCRAと少し異なる静的画像インポートを扱います。CRAでは、画像ファイルをインポートするとパブリック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>タグにwidthheight属性を自動で設定します。これにより、画像がロードされるとレイアウトシフトが防止されます。ただし、画像が寸法の一方だけスタイル指定され、もう一方がautoにスタイル設定されていない場合は問題が発生する可能性があります。autoにスタイル設定されていない場合、寸法は<img>寸法属性の値にデフォルト設定されるため、画像が歪んで表示されることがあります。

<img>タグを保持することで、アプリケーション内の変更を最小限に抑え、上記の問題を回避できます。その後、必要に応じて、loader を設定することで <Image>コンポーネントへの移行を選択するか、または画像の自動最適化機能があるデフォルトのNext.jsサーバーに移行することができます。

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

// Before
import logo from '/logo.png'

// After
import logo from '../public/logo.png'

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

// Before
<img src={logo} />

// After
<img src={logo.src} />

あるいは、画像アセットのパブリックURLをファイル名に基づいて参照することもできます。たとえば、public/logo.pngはアプリケーションで/logo.pngとして画像を配信し、これがsrcの値になります。

Warning: TypeScriptを使用している場合、srcプロパティにアクセスすると型エラーが発生する可能性があります。これを修正するには、next-env.d.tstsconfig.jsonファイルのinclude配列に追加する必要があります。Next.jsはアプリケーションをステップ9で実行すると自動的にこのファイルを生成します。

ステップ8: 環境変数を移行する

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

クライアントサイドで環境変数を公開するために使用されるプレフィックスが主な違いです。すべてのREACT_APP_プレフィックスの環境変数をNEXT_PUBLIC_に変更します。

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

アプリケーションをテストして、Next.jsへの移行が成功したかを確認する準備が整いました。しかし、その前に、package.json内のscriptsをNext.js関連のコマンドで更新し、.gitignoreファイルに .next、および next-env.d.tsを追加する必要があります:

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の利点を十分に活用していませんが、今から段階的な変更を加えて、すべての利点を享受できます。次に行うことをお勧めします:

Good to know: 静的エクスポートを使用することは、現在useParamsフックの使用をサポートしていません