コンテンツにスキップ

Create React Appからの移行

このガイドは、既存のCreate React App (CRA) サイトをNext.jsに移行するのに役立ちます。

なぜ切り替えるのか?

Create React AppからNext.jsに切り替えたい理由はいくつかあります。

初回ページ読み込み時間の遅さ

Create React Appは純粋にクライアントサイドのReactを使用しています。クライアントサイドのみのアプリケーション、別名シングルページアプリケーション (SPA) は、初回ページ読み込み時間が遅いという問題が発生することがよくあります。これはいくつかの理由によります。

  1. ブラウザがReactコードとアプリケーションバンドル全体をダウンロードして実行し、そのコードがデータをロードするためのリクエストを送信できるようになるまで待機する必要があります。
  2. 追加する新しい機能や依存関係が増えるごとに、アプリケーションコードが肥大化します。

自動コード分割がない

前述の読み込み時間の遅さの問題は、コード分割によってある程度軽減できます。しかし、手動でコード分割を行おうとすると、意図せずネットワークウォーターフォールを引き起こす可能性があります。Next.jsは、ルーターとビルドパイプラインに自動コード分割とツリーシェイキングを組み込んで提供しています。

ネットワークウォーターフォール

パフォーマンス低下の一般的な原因は、アプリケーションがデータをフェッチするために連続したクライアント-サーバーリクエストを行うことです。SPAにおけるデータフェッチのパターンの1つは、プレースホルダーをレンダリングし、コンポーネントがマウントされた後にデータをフェッチすることです。残念ながら、子コンポーネントは親が自身のデータの読み込みを完了するまでデータフェッチを開始できないため、「ウォーターフォール」リクエストが発生します。

Next.jsではクライアントサイドのデータフェッチもサポートされていますが、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の設定オプションが格納されます。

next.config.ts
import type { NextConfig } from 'next'
 
const nextConfig: NextConfig = {
  output: 'export', // Outputs a Single-Page Application (SPA)
  distDir: 'build', // Changes the build output directory to `build`
}
 
export default nextConfig

: output: 'export'を使用すると、静的エクスポートを行うことになります。SSRやAPIなどのサーバーサイド機能にはアクセスできません。Next.jsのサーバー機能を活用するには、この行を削除してください。

ステップ3: ルートレイアウトを作成する

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

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

  1. srcディレクトリ内に(またはルートにappを置く場合はプロジェクトルートに)新しいappディレクトリを作成します。
  2. appディレクトリ内に、layout.tsx(またはlayout.js)ファイルを作成します。
app/layout.tsx
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
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のMetadata APITestingセットアップでサポートされています。

ステップ4: メタデータ

Next.jsは<meta charset="UTF-8" /><meta name="viewport" content="width=device-width, initial-scale=1" />タグを自動的に含めるため、<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: スタイル

CRAと同様に、Next.jsはCSS Modulesをすぐにサポートしています。また、グローバルCSSのインポートもサポートしています。

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

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として維持し、すべてのルートをインターセプトしたいため、オプションのキャッチオールルートを使用します。

  1. app内に[[...slug]]ディレクトリを作成します。
app
  [[...slug]]
   page.tsx
  layout.tsx
  1. page.tsxに以下を追加します。:
app/[[...slug]]/page.tsx
export function generateStaticParams() {
  return [{ slug: [''] }]
}
 
export default function Page() {
  return '...' // We'll update this
}

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

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

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

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

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'ディレクティブにより、このファイルはクライアントコンポーネントになります。
  • ssr: falseを指定したdynamicインポートは、<App />コンポーネントのサーバーサイドレンダリングを無効にし、これを真にクライアントオンリー(SPA)にします。

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

app/[[...slug]]/page.tsx
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>width属性とheight属性を画像の寸法に基づいて自動的に設定します。これにより、画像読み込み時のレイアウトシフトが防止されます。ただし、アプリに、片方の寸法のみがスタイル設定されており、もう片方の寸法がautoに設定されていない画像が含まれている場合、問題が発生する可能性があります。autoにスタイル設定されていない場合、寸法は<img>の寸法属性の値にデフォルト設定され、画像が歪んで表示される可能性があります。

<img>タグを維持することで、アプリケーションの変更量を減らし、上記の問題を防ぐことができます。その後、オプションで<Image>コンポーネントに移行し、ローダーを設定して画像を最適化したり、自動画像最適化機能を備えたデフォルトのNext.jsサーバーに移行したりすることができます。

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

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

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

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

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

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

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

Next.jsはCRAと同様に環境変数をサポートしていますが、ブラウザで公開したい変数はNEXT_PUBLIC_というプレフィックスを必要とします

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

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

package.jsonのスクリプトをNext.jsコマンドを使用するように更新します。また、.nextnext-env.d.ts.gitignoreに追加します。

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

これで実行できます

npm run dev

https://: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.jsではnext.config.tsNext.js rewritesでこれを再現できます。

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.jsではnext.config.tsでNext.jsの設定を拡張できます。

next.config.ts
import { NextConfig } from 'next'
 
const nextConfig: NextConfig = {
  webpack: (config, { isServer }) => {
    // Modify the webpack config here
    return config
  },
}
 
export default nextConfig

: これには、devスクリプトから--turbopackを削除してTurbopackを無効にする必要があります。

TypeScriptの設定

Next.jsはtsconfig.jsonがあれば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'を削除してください。