Viteからの移行
このガイドは、既存の Vite アプリケーションを Next.js に移行する際に役立ちます。
なぜ移行するのか?
Vite から Next.js に移行したくなる理由はいくつかあります:
ページの初期読み込み時間が遅い
React 用のデフォルトの Vite プラグインでアプリケーションを構築した場合、アプリケーションは純粋なクライアントサイドアプリケーションです。 クライアントサイドのみのアプリケーションは、シングルページアプリケーション(SPA)としても知られており、ページの初期読み込み時間が遅くなることがよくあります。 これにはいくつかの理由があります:
- ブラウザは、あなたのコードが何らかのデータをロードするリクエストを送信できるようになる前に、React コードとあなたのアプリケーションバンドル全体がダウンロードされ、実行されるのを待つ必要があります。
- アプリケーションのコードは、新しい機能や依存関係を追加するたびに増えていきます。
自動的なコード分割がない
前述した読み込み時間の遅さという問題は、コード分割によってある程度解決することができます。 しかし、手動でコード分割を行おうとすると、パフォーマンスが悪化することが多いです。 手動でコー ド分割すると、不注意でネットワークウォーターフォールが発生しやすくなります。 Next.js は、ルーターに組み込まれた自動コード分割機能を提供します。
ネットワークウォーターフォール
パフォーマンスが低下する一般的な原因は、アプリケーションがデータをフェッチするためにクライアントとサーバー間で連続したリクエストを行う場合に発生します。 SPA におけるデータフェッチの一般的なパターンの 1 つは、最初にプレースホルダをレンダリングし、コンポーネントがマウントされた後にデータをフェッチすることです。 残念ながら、これはデータをフェッチする子コンポーネントが、親コンポーネントが自身のデータのロードを終了するまでフェッチを開始できないことを意味します。
Next.js では、クライアントでのデータ取得がサポートされていますが、データ取得をサーバーに移行するオプションも用意されています。 これは、クライアントとサーバーのウォーターフォールをなくすことができます。
迅速かつ意図的なローディング状態
React Suspense によるストリーミングのビルトイン・サポートにより、 ネットワークウォーターフォールを発生させることなく、UI のどの部分をどの順番で最初にロードするかについて、より意図的に行うことができます。
これにより、読み込みが速くレイアウトがずれることのないページを構築できます。
データフェッチ・ストラテジーの選択
Next.js では、ニーズに応じて、ページやコンポーネントごとにデータ取得方法を選択できます。 ビルド時、サーバーでのリクエスト時、およびクライアントでのリクエスト時において、フェッチすることを決めることができます。 たとえば、ビルド時に CMS からデータをフェッチしてブログ記事をレンダリングし、CDN に効率的にキャッシュすることができます。
Middleware
Next.js Middleware 使うと、リクエストが完了する前にサーバー上でコードを実行できます。 特に、 ユーザが認証専用ページにアクセスしたときにログインページにリダイレクトすることで、認証されていないコンテンツがフラッシュ表示されるのを避けるのに便利です。 Middleware は実験や国際化にも役立ちます。
ビルドインの最適化
画像、フォント、サードパーティのスクリプトは、しばしばアプリケーションのパフォーマンスに大きな影響を与えます。 Next.js には、それらを自動的に最適化する組み込みコンポーネントが用意されています。
移行手順
この移行の目的は、Next.js アプリケーションをできるだけ早く動作させ、Next.js の機能を段階的に導入できるようにすることです。 まず最初に、既存のルーターを移行せずに、純粋なクライアントサイドアプリケーション(SPA)として維持します。 こうすることで、移行プロセスで問題が発生する可能性を最小限に抑え、マージの競合を減らすことができます。
ステップ 1: Next.js の依存関係のインストール
最初にすべきことは、依存関係として next
をインストールすることです:
npm install next@latest
ステップ 2: Next.js の設定ファイルの作成
プロジェクトのルートに next.config.mjs
を作成します。
このファイルには、Next.js の設定オプションが保存されます。
/** @type {import('next').NextConfig} */
const nextConfig = {
output: 'export', // シングルページアプリケーション(SPA)を出力します
distDir: './dist', // ビルドの出力ディレクトリを `./dist/` に変更します
}
export default nextConfig
Good to know: Next.js 設定ファイルには、
.js
または.mjs
を使用できます。
ステップ 3: TypeScript 設定の更新
TypeScript を使用している場合は、Next.js と互換性を持たせるために、tsconfig.json
ファイルを以下のように変更する必要があります。
TypeScript を使用していない場合は、この手順を省略できます。
tsconfig.node.json
のプロジェクト参照を削除します。./dist/types/**/*.ts
と./next-env.d.ts
をinclude
配列に追加します。./node_modules
をexclude
配列に追加します。compilerOptions
内のplugins
配列に{ "name": "next" }
を追加します。:"plugins": [{ "name": "next" }]
esModuleInterop
にtrue
を設定します。:"esModuleInterop": true
jsx
にpreserve
を設定します。:"jsx": "preserve"
allowJs
にtrue
を設定します。:"allowJs": true
forceConsistentCasingIntitles
にtrue
を設定します。:"forceConsistentCasingIntitles": true
incremental
にtrue
を設定します。:"incremental": true
この変更を加えた tsconfig.json
の例です:
{
"compilerOptions": {
"target": "ES2020",
"useDefineForClassFields": true,
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"module": "ESNext",
"esModuleInterop": true,
"skipLibCheck": true,
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "preserve",
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true,
"allowJs": true,
"forceConsistentCasingIntitles": true,
"incremental": true,
"plugins": [{ "name": "next" }]
},
"include": ["./src", "./dist/types/**/*.ts", "./next-env.d.ts"],
"exclude": ["./node_modules"]
}
TypeScript の設定については、Next.js のドキュメントを参照してください。
ステップ 4: ルートレイアウトの作成
Next.js App Router アプリケーションには、ルートレイアウトファイルを含める必要があります。
これは、アプリケーション内のすべてのページをラップする React Server Component です。
このファイルは、app
ディレクトリのトップレベルで定義されます。
Vite アプリケーションのルートレイアウトファイルに最も近いのは index.html
ファイルで、<html>
タグ、<head>
タグ、<body>
タグが含まれています。
このステップでは、index.html
ファイルをルートレイアウトファイルに変換します:
src
ディレクトリに新しいapp
ディレクトリを作成します。- その
app
ディレクトリ内に新しいlayout.tsx
ファイルを作成します:
export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
return null
}
Good to know: レイアウトファイルには、
.js
、.jsx
、または.tsx
の拡張子を使用できます。
index.html
ファイルの内容を、先に作成した<RootLayout>
コンポーネントにコピーし、body.div#root
とbody.script
タグを<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" type="image/svg+xml" href="/icon.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>My App</title>
<meta name="description" content="My App is a..." />
</head>
<body>
<div id="root">{children}</div>
</body>
</html>
)
}
- Next.js にはデフォルトで meta charset タグと meta viewport タグが含まれているので、
<head>
からそれらを削除しても問題ありません:
export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<html lang="en">
<head>
<link rel="icon" type="image/svg+xml" href="/icon.svg" />
<title>My App</title>
<meta name="description" content="My App is a..." />
</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>My App</title>
<meta name="description" content="My App is a..." />
</head>
<body>
<div id="root">{children}</div>
</body>
</html>
)
}
- 最後に、Next.js は最後の
<head>
タグをメタデータ API で管理できます。最終的なメタデータ情報を、エクスポートされたmetadata
オブジェクトに移動します:
import type { Metadata } from 'next'
export const metadata: Metadata = {
title: 'My App',
description: 'My App is a...',
}
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: エントリーポイントページの作成
Next.js では、page.tsx
ファイルを作成してアプリケーションのエントリーポイントを宣言します。
Vite でこのファイルに最も近いのは、main.tsx
ファイルです。
このステップでは、アプリケーションのエントリポイントを設定します。
app
ディレクトリに[[...slug]]
ディレクトリを作成します。
このガイドでは、まず Next.js を SPA(シングルページアプリケーション)としてセットアップすることを目的としているので、
アプリケーションのすべての可能な経路をキャッチするページのエントリポイントが必要です。
そのためには、アプリディレクトリに新しい [[...slug]]
ディレクトリを作成します。
このディレクトリは、オプションのキャッチオールルートセグメントと呼ばれるものです。
Next.js では、ディレクトリを使ってルートを定義するファイルシステムベースのルーターを使います。
この特別なディレクトリは、アプリケーションのすべてのルートが page.tsx
ファイルを含むディレクトリに誘導されるようにします。
app/[[...slug]]
ディレクトリ内に、次の内容で新しいpage.tsx
ファイルを作成します:
import '../../index.css'
export function generateStaticParams() {
return [{ slug: [''] }]
}
export default function Page() {
return '...' // We'll update this
}
Good to know: ページファイルには、
.js
、.jsx
、または.tsx
の拡張子を使用できます。
このファイルは Server Component です。
next build
を実行すると、このファイルは静的アセットにプリレンダリングされます。動的なコードは必要ありません。
このファイルはグローバル CSS をイン ポートし、generateStaticParams
に 1 つのルート(/
のインデックスルート)だけを生成するように指示します。
さて、クライアントのみで実行される Vite アプリケーションの残りの部分を動かしてみましょう。
'use client'
import React from 'react'
import dynamic from 'next/dynamic'
const App = dynamic(() => import('../../App'), { ssr: false })
export function ClientOnly() {
return <App />
}
このファイルは use client
ディレクティブで定義されたClient Component です。
Client Component は、クライアントに送信される前にサーバ上で HTML にプリレンダリングされます。
クライアント専用のアプリケーションを起動したいので、App
コンポーネントから下のプリレンダリングを無効にするように Next.js
を設定します。
const App = dynamic(() => import('../../App'), { ssr: false })
では、新しいコンポーネントを使うようにエントリーポイントページを更新してください:
import '../../index.css'
import { ClientOnly } from './client'
export function generateStaticParams() {
return [{ slug: [''] }]
}
export default function Page() {
return <ClientOnly />
}
ステップ 6: 静的画像インポートの更新
Next.js では、静的画像のインポート処理が Vite と若干異なります。 Vite では、画像ファイルをインポートするとその公開 URL が文字列として返されます:
import image from './img.png' // 本番では `image` は '/assets/img.2d8efhg.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>
タグを維持することで、アプリケーションの変更量を減らし、上記の問題を防ぐことができます。
その後、オプションで <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
の値として使用できます。
注意: TypeScript を使用している場合、
src
プロパティにアクセスする際に型エラーが発生する可能性があります。 今のところは無視してかまいません。このガイドの終わりまでには修正されるでしょう。
ステップ 7: 環境変数の移行
Next.js は Vite と同様に .env
環境変数をサポートしています。
主な違いは、クライアントサイドで環境変数を公開するためのプレフィックスです。
- プレフィックスが
VITE_
の環境変数はすべてNEXT_PUBLIC_
に変更してください。
Vite では、Next.js ではサポートされていないいくつかの組み込み環境変数を特別な import.meta.env
オブジェクトで公開しています。
以下のように使用方法を更新する必要があります:
import.meta.env.MODE
⇒process.env.NODE_ENV
import.meta.env.PROD
⇒process.env.NODE_ENV === 'production'
import.meta.env.DEV
⇒process.env.NODE_ENV !== 'production'
import.meta.env.SSR
⇒typeof window !== 'undefined'
Next.js には、組み込みの BASE_URL
環境変数もありません。しかし、必要であれば設定することができます:
.env
ファイルに以下を追加します:
# ...
NEXT_PUBLIC_BASE_PATH="/some-base-path"
next.config.mjs
ファイルのprocess.env.NEXT_PUBLIC_BASE_PATH
にbasePath
を設定します:
/** @type {import('next').NextConfig} */
const nextConfig = {
output: 'export', // シングルページアプリケーション(SPA)を出力します
distDir: './dist', // ビルドの出力ディレクトリを `./dist/` に変更します
basePath: process.env.NEXT_PUBLIC_BASE_PATH, // basePath を `/some-base-path` に設定します
}
export default nextConfig
import.meta.env.BASE_URL
の使用法をprocess.env.NEXT_PUBLIC_BASE_PATH
に更新する。
ステップ 8: package.json
のスクリプトの更新
これで、もし Next.js への移行が成功していればアプリケーションを実行できるようになります。
しかしその前に、package.json
の scripts
を Next.js 関連のコマンドで更新し、.next
と next-env.d.ts
を .gitignore
に追加する必要があります:
{
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start"
}
}
# ...
.next
next-env.d.ts
dist
npm run dev
を実行し、http://localhost:3000
を開きます。
すると、Next.js 上でアプリケーションが動いているのが見えるはずです。
例: Next.js に移行した Vite アプリケーションの動作例については、こちらのプルリクエストをご覧ください。
ステップ 9: クリーンアップ
これでコードベースから Vite 関連の成果物を一掃できます:
main.tsx
を削除します。index.html
を削除します。vite-env.d.ts
を削除します。tsconfig.node.json
を削除します。vite.config.ts
を削除します。- Vite の依存関係をアンインストールします。
次のステップ
すべてが計画どおりに進んでいれば、Next.js アプ リケーションはシングルページアプリケーションとして動作しています。 まだ Next.js のメリットのほとんどを活用できていませんが、あなたは今すべての利点を享受するために漸進的な変更を開始することができます。次にやるべきことは以下の通りです:
- React Router から Next.js App Router に移行して、次のことを実現します。
- コードの自動分割
- ストリーミングサーバーレンダリング
- React Server Components
<Image>
コンポーネントで画像を最適化しますnext/font
でフォントを最適化します<Script>
コンポーネントでサードパーティ製スクリプトを最適化します- Next.js ルールをサポートするように ESLint の設定を更新します