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

Viteからの移行

このガイドは、既存のViteアプリケーションをNext.jsに移行する方法を説明します。

なぜ切り替えるのか?

ViteからNext.jsへの切り替えを検討する理由はいくつかあります:

初期ページの読み込み時間が遅い

デフォルトのViteプラグイン for Reactを使用してアプリケーションを構築した場合、アプリケーションは純粋にクライアントサイドのアプリケーションとなります。クライアントサイドオンリーのアプリケーション、別名シングルページアプリケーション(SPA)は、しばしば初期ページの読み込み時間が遅くなることがあります。これはいくつかの理由により発生します:

  1. ブラウザがReactコードとアプリケーション全体のバンドルをダウンロードして実行するまで、コードがリクエストを送信してデータを読み込むことができません。
  2. アプリケーションのコードは、新しい機能や追加の依存関係を追加するたびに成長します。

自動コード分割がない

以前の読み込み時間が遅くなる問題は、ある程度コード分割で管理できます。しかし、手動でコード分割を行おうとすると、多くの場合、パフォーマンスが悪化します。手動でコード分割することで、ネットワークウォーターフォールを意図せずに導入することが簡単です。Next.jsは自動コード分割が組み込まれたルーターを提供します。

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

パフォーマンスの悪化を引き起こす一般的な原因は、アプリケーションがデータを取得するためにクライアント-サーバー間で連続的なリクエストを行うことです。SPAでデータを取得する一般的なパターンは、最初にプレースホルダーをレンダリングし、コンポーネントがマウントされた後にデータを取得することです。残念ながら、これはデータを取得する子コンポーネントが親コンポーネントのデータ読み込み完了までデータ取得を開始できないことを意味します。

クライアントでのデータ取得はNext.jsでもサポートされていますが、データ取得をサーバーにシフトすることで、クライアント-サーバーのウォーターフォールを排除するオプションも提供します。

高速で意図的なローディング状態

React Suspenseを介したストリーミングに対する組み込みサポートを利用することで、ネットワークウォーターフォールを引き起こすことなく、UIのどの部分を最初にロードするか、どの順序でロードするかについてより意図的であることができます。

これにより、ページがより迅速に読み込まれるようになり、レイアウトシフトを排除できます。

データ取得戦略を選択する

ニーズに応じて、Next.jsではページおよびコンポーネント単位でデータ取得戦略を選択できます。ビルド時、サーバーのリクエスト時、またはクライアントでデータを取得することを決定できます。たとえば、CMSからデータを取得し、ブログ投稿をビルド時にレンダリングし、効率的にCDNでキャッシュできます。

ミドルウェア

Next.js Middlewareは、リクエストが完了する前にサーバー上でコードを実行することを可能にします。これは、ユーザーが認証専用のページにアクセスした際、未認証のコンテンツのフラッシュを避けるために、ユーザーをログインページにリダイレクトするのに特に有用です。ミドルウェアは、実験や国際化にも有用です。

組み込みの最適化

画像フォント、およびサードパーティのスクリプトは、アプリケーションのパフォーマンスに大きく影響することがあります。Next.jsには、それらを自動的に最適化する組み込みのコンポーネントが備わっています。

移行手順

この移行の目的は可能な限り速く動作するNext.jsアプリケーションを構築することであり、その後、Next.jsの機能を段階的に採用できるようにすることです。まずは、既存のルーターを移行せずに、純粋にクライアントサイドアプリケーション(SPA)として維持することにします。これにより、移行プロセス中に問題に直面する可能性を最小限に抑え、マージの競合を減少させます。

ステップ1: Next.js依存関係のインストール

最初に行うべきことは、依存関係としてnextをインストールすることです:

Terminal
npm install next@latest

ステップ2: Next.js構成ファイルの作成

プロジェクトのrootにnext.config.mjsを作成します。このファイルはNext.jsの構成オプションを保持します。

next.config.mjs
/** @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を使用していない場合、このステップをスキップしてください。

  1. tsconfig.node.jsonへのプロジェクト参照を削除する
  2. include配列./dist/types/**/*.ts./next-env.d.tsを追加する
  3. exclude配列./node_modulesを追加する
  4. compilerOptionsplugins配列内に{ "name": "next" }を追加する: "plugins": [{ "name": "next" }]
  5. esModuleInteroptrueに設定する: "esModuleInterop": true
  6. jsxpreserveに設定する: "jsx": "preserve"
  7. allowJstrueに設定する: "allowJs": true
  8. forceConsistentCasingInFileNamestrueに設定する: "forceConsistentCasingInFileNames": true
  9. incrementaltrueに設定する: "incremental": true

これらの変更を行ったtsconfig.jsonの作例は以下の通りです:

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,
"forceConsistentCasingInFileNames": true,
"incremental": true,
"plugins": [{ "name": "next" }]
},
"include": ["./src", "./dist/types/**/*.ts", "./next-env.d.ts"],
"exclude": ["./node_modules"]
}

TypeScriptの設定に関する詳細情報は、Next.jsドキュメントを参照してください。

ステップ4: Root レイアウトの作成

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

Viteアプリケーションにおける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: .js.jsx、または .tsx の拡張子はLayoutファイルに使用できます。

  1. index.htmlファイルの内容を、<RootLayout>コンポーネントにコピーし、body.div#rootbody.scriptタグを<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" 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>
)
}
  1. Next.jsにはデフォルトでメタ文字セットメタビューポートタグが含まれているため、<head>からそれらを安全に削除できます:
app/layout.tsx
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>
)
}
  1. 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>My App</title>
<meta name="description" content="My App is a..." />
</head>
<body>
<div id="root">{children}</div>
</body>
</html>
)
}
  1. 最後に、Next.jsはメタデータAPIで最後の<head>タグを管理できます。エクスポートされたmetadataオブジェクトに最終的なメタデータ情報を移動します:
app/layout.tsx
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の規約に基づくアプローチを使用してシフトしました。このアプローチにより、ページのSEOとWeb共有可能性をより簡単に向上させることができます。

ステップ5: エントリーポイントページの作成

Next.jsでは、page.tsxファイルを作成することでアプリケーションのエントリーポイントを宣言します。 Viteにおけるこのファイルの最も近い等価物は、main.tsxファイルです。このステップでは、アプリケーションのエントリーポイントをセットアップします。

  1. appディレクトリに[[...slug]]ディレクトリを作成してください。

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

このディレクトリはオプションのキャッチオールルートセグメントと呼ばれます。Next.jsはディレクトリがルートを定義するために使用されるファイルベースのルーターを使用しています。この特別なディレクトリは、アプリケーションのすべてのルートをその中にあるpage.tsxファイルに向かわせるよう保証します。

  1. app/[[...slug]]ディレクトリ内に新しいpage.tsxファイルを以下の内容で作成してください:
app/[[...slug]]/page.tsx
import '../../index.css'

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

export default function Page() {
return '...' // This will be updated
}

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

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

このファイルはグローバルCSSをインポートし、generateStaticParamsには一つのルート、すなわち/のインデックスルートのみを生成することを伝えています。

では、クライアントのみで実行されるViteアプリケーションの残りを移動させます。

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

import React from 'react'
import dynamic from 'next/dynamic'

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

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

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

クライアントのみのアプリケーションを開始したいので、Next.jsを設定してAppコンポーネント以下のプリレンダリングを無効にすることができます。

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

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

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

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

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

ステップ6: 静的イメージインポートの更新

Next.jsは静的イメージインポートをViteと少し異なった方法で扱います。Viteでは、イメージファイルをインポートするとその公開URLが文字列として返されます:

App.tsx
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サーバーに移行できます。

  1. /publicからインポートされたイメージの絶対インポートパスを相対インポートに変換します:
// Before
import logo from '/logo.png'

// After
import logo from '../public/logo.png'
  1. <img>タグにイメージオブジェクト全体ではなくsrcプロパティを渡します:
// Before
<img src={logo} />

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

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

Warning: TypeScriptを使用している場合、srcプロパティにアクセスする際に型エラーが発生する可能性があります。しかし、これらは今は無視して問題ありません。これらはこのガイドの終わりまでには解決されます。

ステップ7: 環境変数の移行

Next.jsはViteに似た環境変数.envをサポートしています。主な違いは、クライアント側に環境変数を公開する際に使用されるプレフィックスです。

  • VITE_プレフィックスの環境変数をNEXT_PUBLIC_に変更してください。

ViteはNext.jsではサポートされていない特別なimport.meta.envオブジェクト上にいくつかの組み込みの環境変数を公開しています。それらの使用を次のように更新する必要があります:

  • import.meta.env.MODEprocess.env.NODE_ENV
  • import.meta.env.PRODprocess.env.NODE_ENV === 'production'
  • import.meta.env.DEVprocess.env.NODE_ENV !== 'production'
  • import.meta.env.SSRtypeof window !== 'undefined'

Next.jsは組み込みのBASE_URL環境変数も提供していません。ただし、必要に応じて設定することは可能です:

  1. .envファイルに次の内容を追加します:
.env
# ... \{#}
NEXT_PUBLIC_BASE_PATH="/some-base-path"
  1. next.config.mjsファイルでbasePathprocess.env.NEXT_PUBLIC_BASE_PATHに設定します:
next.config.mjs
/** @type {import('next').NextConfig} */
const nextConfig = {
output: 'export', // シングルページアプリケーション(SPA)を出力します。
distDir: './dist', // ビルド出力ディレクトリを`./dist/`に変更します。
basePath: process.env.NEXT_PUBLIC_BASE_PATH, // ベースパスを`/some-base-path`に設定します。
}

export default nextConfig
  1. import.meta.env.BASE_URLの使用をprocess.env.NEXT_PUBLIC_BASE_PATHに更新します

ステップ8: package.json のスクリプトの更新

これでアプリケーションを実行して、Next.jsへの移行がうまくいったかどうかをテストできます。しかしその前に、package.jsonのスクリプトをNext.js関連のコマンドに更新し、.gitignore.nextnext-env.d.tsを追加する必要があります:

package.json
{
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start"
}
}
.gitignore
# ... \{#}
.next
next-env.d.ts
dist

次に、npm run devを実行し、http://localhost:3000を開いてください。Next.js上でアプリケーションが実行されているのが確認できるはずです。

Example: ViteアプリケーションをNext.jsに移行する作業例については、このプルリクエストをチェックしてください。

ステップ9: クリーンアップ

Vite関連のアーティファクトからコードベースをクリーンアップできます:

  • main.tsxを削除
  • index.htmlを削除
  • vite-env.d.tsを削除
  • tsconfig.node.jsonを削除
  • vite.config.tsを削除
  • Viteの依存関係をアンインストール

次のステップ

すべてが計画どおりに進んだ場合、次のステップとして、単一ページアプリケーションとして動作するNext.jsアプリケーションがあるはずです。ただし、まだNext.jsの多くの利点を活用していませんが、今後は段階的な変更を行い、すべての利点を享受することができます。次に行うべきことは以下です: