Create React Appからの移行
このガイドは、既存のCreate React App(CRA)サイトをNext.jsに移行する際に役立ちます。
なぜ移行するのか?
Create React AppからNext.jsに移行する理由はいくつかあります:
初期ページの読み込み時間が遅い
Create React Appは純粋にクライアントサイドのReactを使用しています。クライアントサイドのみのアプリケーション(シングルページアプリケーション(SPA)とも呼ばれる)は、初期ページの読み込み時間が遅くなりがちです。これは主に以下の理由によるものです:
- ブラウザはReactコードとアプリケーション全体のバンドルがダウンロードされ実行されるのを待ってから、データを読み込むためのリクエストを送信できるようになります。
- アプリケーションコードは新機能や依存関係を追加するたびに大きくなります。
自動コード分割がない
前述の読み込み時間の遅さの問題は、コード分割である程度緩和できます。しかし、手動でコード分割を行おうとすると、意図せずネットワークウォーターフォールを引き起こす可能性があります。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)として扱います。これにより、複雑さとマージの競合を減らすことができます。
注意:
package.json
のカスタムhomepage
フィールド、カスタムサービスワーカー、または特定のBabel/webpackの調整などの高度なCRA構成を使用している場合は、このガイドの最後にある追加の考慮事項セクションで、これらの機能をNext.jsで複製または適応するためのヒントを参照してください。
ステップ1:Next.jsの依存関係をインストールする
既存のプロジェクトにNext.jsをインストールします:
npm install next@latest
ステップ2:Next.js設定ファイルを作成する
プロジェクトのルート(package.json
と同じレベル)にnext.config.ts
を作成します。このファイルにはNext.jsの設定オプションが含まれます。
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>
タグが含まれています。
src
ディレクトリ内(またはルートにapp
を配置したい場合はプロジェクトのルート)に新しいapp
ディレクトリを作成します。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はメタデータ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.ico
、icon.png
、robots.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はメタデータ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の規約ベースのアプローチ(メタデータAPI)に移行しました。このアプローチにより、ページのSEOとウェブ共有性を簡単に向上させることができます。
ステップ5:スタイル
CRAと同様に、Next.jsはデフォルトでCSSモジュールをサポートしています。また、グローバル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ルーター)では、app
ディレクトリ内の各フォルダがルートに対応し、各フォルダにはpage.tsx
が必要です。
今はアプリをSPAとして保持し、すべてのルートをインターセプトしたいので、オプショナルキャッチオールルートを使用します。
app
内に[[...slug]]
ディレクトリを作成します。
app
┣ [[...slug]]
┃ ┗ page.tsx
┣ layout.tsx
page.tsx
に以下を追加します:
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
)を作成します:
'use client'
import dynamic from 'next/dynamic'
const App = dynamic(() => import('../../App'), { ssr: false })
export function ClientOnly() {
return <App />
}
'use client'
ディレクティブによりこのファイルはクライアントコンポーネントになります。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>
の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でアプリケーションを実行するときに、このファイルを自動的に生成します。
ステップ9:環境変数を移行する
Next.jsはCRAと同様に環境変数をサポートしていますが、ブラウザで公開したい変数にはNEXT_PUBLIC_
プレフィックスが必要です。
主な違いは、クライアントサイドで環境変数を公開するために使用されるプレフィックスです。REACT_APP_
プレフィックスを持つすべての環境変数をNEXT_PUBLIC_
に変更します。
ステップ10:package.json
のスクリプトを更新する
package.json
のスクリプトをNext.jsコマンドを使用するように更新します。また、.gitignore
に.next
とnext-env.d.ts
を追加します:
{
"scripts": {
"dev": "next dev --turbopack",
"build": "next build",
"start": "npx serve@latest ./build"
}
}
# ...
.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.json
でhomepage
フィールドを使用して特定のサブパスでアプリを提供していた場合、Next.jsではnext.config.ts
のbasePath
設定を使用して同様の機能を実現できます:
import { NextConfig } from 'next'
const nextConfig: NextConfig = {
basePath: '/my-subpath',
// ...
}
export default nextConfig
カスタムService Worker
の処理
CRAのサービスワーカー(例:create-react-app
のserviceWorker.js
)を使用していた場合、Next.jsでプログレッシブウェブアプリケーション(PWA)を作成する方法を学ぶことができます。
APIリクエストのプロキシ
CRAアプリがpackage.json
のproxy
フィールドを使用してバックエンドサーバーにリクエストを転送していた場合、next.config.ts
のNext.jsリライトでこれを複製できます:
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の設定を拡張できます:
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.json
のinclude
配列に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の機能はまだ活用していませんが、段階的に導入することができます:
- React Routerから移行してNext.js Appルーターを使用することで以下の機能が得られます:
- 自動コード分割
- ストリーミングサーバーレンダリング
- Reactサーバーコンポーネント
<Image>
コンポーネントで画像を最適化next/font
でフォントを最適化<Script>
コンポーネントでサードパーティのスクリプトを最適化npx next lint
を実行してNext.jsの推奨ルールでESLintを有効化し、プロジェクトのニーズに合わせて設定する
注意: 静的エクスポート(
output: 'export'
)を使用すると、useParams
フックやその他のサーバー機能が現在サポートされていません。すべてのNext.js機能を使用するには、next.config.ts
からoutput: 'export'
を削除してください。