How to migrate from Create React App to Next.js
このガイドは、既存のCreate React App(CRA)サイトをNext.jsに移行するのに役立ちます。
なぜ切り替えるのか?
Create React AppからNext.jsに切り替えたい理由はいくつかあります。
初期ページ読み込み時間が遅い
Create React Appは純粋にクライアント側でレンダリングします。クライアント側のみのアプリケーション(シングルページアプリケーション(SPA)とも呼ばれます)は、初期ページ読み込み時間が遅くなることがよくあります。これは次の理由により発生します。
- ブラウザはReactコードとアプリケーション全体のバンドルがダウンロードおよび実行されるまで待つ必要があり、その後にコードがデータを読み込むリクエストを送信できるようになります。
- 新しい機能と依存関係を追加するたびに、アプリケーションコードが増加します。
自動コード分割がない
前述の読み込み時間の問題は、コード分割である程度軽減できます。しかし、手動でコード分割を行おうとすると、意図しないネットワークウォーターフォールを引き起こす可能性があります。Next.jsはルーターとビルドパイプラインに自動コード分割とツリーシェーキングが組み込まれています。
ネットワークウォーターフォール
パフォーマンスの低下の一般的な原因は、データを取得するために順序立てられたクライアント・サーバー間リクエストを行うアプリケーションです。SPAでのデータ取得パターンの1つは、プレースホルダーをレンダリングしてからコンポーネントがマウント後にデータを取得することです。残念ながら、子コンポーネントは親が独自のデータの読み込みを完了してからしかデータ取得を開始できないため、リクエストの「ウォーターフォール」が生じます。
クライアント側のデータ取得はNext.jsでもサポートされていますが、Next.jsではデータ取得をサーバーに移動することもできます。これにより、多くの場合、クライアント・サーバー間のウォーターフォールが完全に排除されます。
高速で意図的な読み込み状態
React Suspenseを使用したストリーミングの組み込みサポートにより、ネットワークウォーターフォールを作成することなく、UIのどの部分が最初に読み込まれるか、どの順序で読み込まれるかを定義できます。
これにより、読み込みが高速でレイアウトシフトのないページを構築できます。
データ取得戦略を選択する
ニーズに応じて、Next.jsではページまたはコンポーネントレベルでデータ取得戦略を選択できます。たとえば、CMSからデータを取得して、高速な読み込み速度のためにブログ投稿をビルド時(SSG)でレンダリングするか、必要に応じてリクエスト時(SSR)にデータを取得できます。
プロキシ
Next.js Proxyを使用すると、リクエストが完了する前にサーバー上でコードを実行できます。たとえば、認証済みのみのページのユーザーをプロキシ内のログインページにリダイレクトすることで、未認証コンテンツのフラッシュを回避できます。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 Routerアプリケーションは、すべてのページをラップするルートレイアウトファイル(React Server Component)を含める必要があります。
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とWebシェアラビリティを、より簡単に改善できます。
ステップ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 Router)では、appディレクトリ内の各フォルダはルートに対応し、各フォルダはpage.tsxを持つ必要があります。
今はアプリをSPAとして保ち、すべてのルートをインターセプトしたいため、オプショナルキャッチオールルートを使用します。
app内に[[...slug]]ディレクトリを作成します。
app
┣ [[...slug]]
┃ ┗ page.tsx
┣ layout.tsxpage.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にします。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属性を自動的に設定します。これは画像が読み込まれるときのレイアウトシフトを防ぎます。ただし、これは、アプリに、1つのディメンションのみがスタイル設定されていて、他がautoにスタイル設定されていない画像が含まれている場合に問題を引き起こす可能性があります。autoにスタイル設定されていない場合、ディメンションは<img>ディメンション属性の値にデフォルト設定されます。これにより、画像が歪んで見える可能性があります。
<img>タグを保持すると、アプリケーション内の変更量が減少し、上記の問題が防止されます。その後、ローダーを設定して画像を最適化することにより、オプションで後で<Image>コンポーネントに移行することができます。または、デフォルトのNext.jsサーバーに移行できます。このサーバーは自動画像最適化を備えています。
/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プロパティにアクセスするときにタイプエラーが発生する可能性があります。修正するには、next-env.d.tsをtsconfig.jsonファイルのinclude配列に追加する必要があります。Next.jsはステップ9でアプリケーションを実行するときに、このファイルを自動的に生成します。
ステップ9:環境変数を移行する
Next.jsは環境変数をCRAと同様にサポートしていますが、ブラウザに公開したい変数にはNEXT_PUBLIC_プレフィックスが必須です。
主な違いは、クライアント側で環境変数を公開するために使用されるプレフィックスです。REACT_APP_プレフィックスを持つすべての環境変数をNEXT_PUBLIC_に変更します。
ステップ10:package.jsonのスクリプトを更新する
Next.jsコマンドを使用するためにpackage.jsonスクリプトを更新します。また、.nextとnext-env.d.tsを.gitignoreに追加します。
{
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "npx serve@latest ./build"
}
}# ...
.next
next-env.d.tsこれで以下を実行できます。
npm run devhttp://localhost:3000を開きます。アプリケーションがNext.js上で(SPAモード)実行されているのを確認できるはずです。
ステップ11:クリーンアップ
Create React App固有のアーティファクトを削除できます。
public/index.htmlsrc/index.tsxsrc/react-app-env.d.tsreportWebVitalsセットアップreact-scripts依存関係(package.jsonからアンインストール)
追加の考慮事項
CRAでカスタムhomepageを使用する
CRA package.jsonのhomepageフィールドを使用してアプリを特定のサブパス下で提供した場合、next.config.tsのbasePath設定を使用してNext.jsでそれを複製できます。
import { NextConfig } from 'next'
const nextConfig: NextConfig = {
basePath: '/my-subpath',
// ...
}
export default nextConfigカスタムService Workerを処理する
CRAのサービスワーカー(例えば、create-react-appからのserviceWorker.js)を使用した場合、Next.jsでProgressive Web Applications(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
CRAでカスタムwebpackまたはBabel設定がある場合、next.config.tsでNext.jsのconfig を拡張できます。
import { NextConfig } from 'next'
const nextConfig: NextConfig = {
webpack: (config, { isServer }) => {
// ここでwebpackconfigを変更します
return config
},
}
export default nextConfig注意:これには、
devスクリプトに--webpackを追加してWebpackを使用する必要があります。
TypeScript設定
tsconfig.jsonがある場合、Next.jsは自動的にTypeScriptを設定します。next-env.d.tsがtsconfig.jsonのinclude配列に記載されていることを確認してください。
{
"include": ["next-env.d.ts", "app/**/*", "src/**/*"]
}バンドラー互換性
Create React AppはWebpackをバンドリングに使用します。Next.jsは現在、ローカル開発をより高速にするためにTurbopackをデフォルトにしています。
next dev # デフォルトではTurbopackを使用Webpackを使用する場合(CRAと同様):
next dev --webpackCRAから高度なWebpack設定を移行する必要がある場合は、引き続きカスタムWebpack設定を提供できます。
次のステップ
すべてがうまくいった場合、シングルページアプリケーションとして実行されている機能的なNext.jsアプリケーションが得られました。サーバー側レンダリングやファイルベースのルーティングなどのNext.js機能をまだ活用していませんが、段階的に行うことができます。
- React Routerから移行する:以下のためのNext.js App Routerへ。
- 画像を最適化する:
<Image>コンポーネントで。 - フォントを最適化する:
next/fontで。 - サードパーティスクリプトを最適化する:
<Script>コンポーネントで。 - ESLintを有効にする:Next.js 推奨ルールで。
注意:静的エクスポート(
output: 'export')を使用することは、現在、useParamsフックまたはその他のサーバー機能をサポートしていません。すべてのNext.js機能を使用するには、next.config.tsからoutput: 'export'を削除します。