Pages Router から App Router への移行方法
このガイドでは以下の内容を説明します:
アップグレード
Node.jsバージョン
Node.jsの最低必要バージョンは v18.17 になりました。詳細はNode.jsドキュメントをご覧ください。
Next.jsバージョン
Next.jsバージョン13に更新するには、以下のコマンドを実行してください:
npm install next@latest react@latest react-dom@latestESLintバージョン
ESLintを使用している場合、ESLintのバージョンもアップグレードが必要です:
npm install -D eslint-config-next@latest補足: VS CodeでESLintの変更を反映させるには、ESLintサーバーを再起動する必要がある場合があります。コマンドパレット(Macは
cmd+shift+p、Windowsはctrl+shift+p)を開き、ESLint: Restart ESLint Serverを検索してください。
次のステップ
更新後は以下のセクションを参照してください:
- 新機能のアップグレード: ImageコンポーネントやLinkコンポーネントの改善など、新機能へのアップグレードガイド
pagesからappディレクトリへの移行:pagesからappディレクトリへ段階的に移行するステップバイステップガイド
新機能のアップグレード
Next.js 13では新しいApp Routerが導入され、新機能と規約が追加されました。新しいRouterはappディレクトリで利用可能で、pagesディレクトリと共存できます。
Next.js 13へのアップグレードはApp Routerの使用を必須としません。更新されたImageコンポーネント、Linkコンポーネント、Scriptコンポーネント、フォント最適化など、pagesでも動作する新機能を引き続き使用できます。
<Image/>コンポーネント
Next.js 12ではnext/future/imageという一時的なインポートでImageコンポーネントの改善が導入されました。これらの改善には、クライアントサイドJavaScriptの削減、画像の拡張とスタイリングの簡素化、アクセシビリティの向上、ネイティブのブラウザ遅延読み込みが含まれます。
バージョン13では、この新しい動作がnext/imageのデフォルトになりました。
新しいImageコンポーネントへの移行を支援する2つのコードモードがあります:
next-image-to-legacy-imageコードモード:next/imageインポートを安全に自動でnext/legacy/imageにリネームします。既存コンポーネントは同じ動作を維持します。next-image-experimentalコードモード: インラインスタイルを追加し、未使用のプロパティを削除します。このコードモードを使用するには、まずnext-image-to-legacy-imageコードモードを実行する必要があります。
<Link>コンポーネント
<Link>コンポーネントは、子要素として手動で<a>タグを追加する必要がなくなりました。この動作はバージョン12.2で実験的オプションとして追加され、現在はデフォルトになりました。Next.js 13では、<Link>は常に<a>をレンダリングし、基礎となるタグにプロパティを転送できます。
例:
import Link from 'next/link'
// Next.js 12: `<a>`をネストしないと除外される
<Link href="/about">
<a>About</a>
</Link>
// Next.js 13: `<Link>`は常に内部で`<a>`をレンダリング
<Link href="/about">
About
</Link>Next.js 13へのリンクのアップグレードには、new-linkコードモードを使用できます。
<Script>コンポーネント
next/scriptの動作はpagesとappの両方をサポートするように更新されましたが、スムーズな移行のために以下の変更が必要です:
- 以前
_document.jsに含めていたbeforeInteractiveスクリプトは、ルートレイアウトファイル(app/layout.tsx)に移動してください。 - 実験的な
worker戦略はまだappで動作せず、この戦略で指定されたスクリプトは削除するか、別の戦略(例:lazyOnload)を使用するように変更する必要があります。 onLoad、onReady、onErrorハンドラはサーバーコンポーネントでは動作しないため、クライアントコンポーネントに移動するか、完全に削除してください。
フォント最適化
以前、Next.jsはフォントCSSのインライン化によってフォントの最適化を支援していました。バージョン13では新しいnext/fontモジュールが導入され、優れたパフォーマンスとプライバシーを確保しながらフォント読み込みエクスペリエンスをカスタマイズできるようになりました。next/fontはpagesとappディレクトリの両方でサポートされています。
CSSのインライン化はpagesでは引き続き動作しますが、appでは動作しません。next/fontを使用してください。
next/fontの使用方法については、フォント最適化ページを参照してください。
pagesからappへの移行
🎥 動画: App Routerの段階的な採用方法 → YouTube (16分)。
App Routerへの移行は、サーバーコンポーネント、Suspenseなど、Next.jsが基盤とするReact機能を初めて使用する機会かもしれません。特殊ファイルやレイアウトなどの新しいNext.js機能と組み合わせると、移行には新しい概念、メンタルモデル、動作変更の学習が必要です。
これらの更新の複雑さを軽減するために、移行を小さなステップに分割することをお勧めします。appディレクトリは、pagesディレクトリと同時に動作するように意図的に設計されており、ページごとに段階的に移行できます。
appディレクトリはネストされたルートとレイアウトをサポートします。詳細を学ぶ。- ネストされたフォルダを使用してルートを定義し、特別な
page.jsファイルを使用してルートセグメントを公開します。詳細を学ぶ。 - 特殊ファイル規約を使用して、各ルートセグメントのUIを作成します。最も一般的な特殊ファイルは
page.jsとlayout.jsです。page.jsはルート固有のUIを定義します。layout.jsは複数のルートで共有されるUIを定義します。- 特殊ファイルには
.js、.jsx、.tsxファイル拡張子を使用できます。
- コンポーネント、スタイル、テストなど、他のファイルを
appディレクトリ内に配置できます。詳細を学ぶ。 getServerSidePropsやgetStaticPropsなどのデータ取得関数は、app内の新しいAPIに置き換えられました。getStaticPathsはgenerateStaticParamsに置き換えられました。pages/_app.jsとpages/_document.jsは、単一のapp/layout.jsルートレイアウトに置き換えられました。詳細を学ぶ。pages/_error.jsは、より細かいerror.js特殊ファイルに置き換えられました。詳細を学ぶ。pages/404.jsはnot-found.jsファイルに置き換えられました。pages/api/*APIルートはroute.js(ルートハンドラ)特殊ファイルに置き換えられました。
ステップ1: appディレクトリの作成
最新のNext.jsバージョン(13.4以上が必要)に更新します:
npm install next@latest次に、プロジェクトのルート(またはsrc/ディレクトリ)に新しいappディレクトリを作成します。
ステップ2: ルートレイアウトの作成
appディレクトリ内に新しいapp/layout.tsxファイルを作成します。これはルートレイアウトで、app内のすべてのルートに適用されます。
export default function RootLayout({
// レイアウトはchildrenプロパティを受け入れる必要があります
// これはネストされたレイアウトまたはページで埋められます
children,
}: {
children: React.ReactNode
}) {
return (
<html lang="en">
<body>{children}</body>
</html>
)
}export default function RootLayout({
// レイアウトはchildrenプロパティを受け入れる必要があります
// これはネストされたレイアウトまたはページで埋められます
children,
}) {
return (
<html lang="en">
<body>{children}</body>
</html>
)
}appディレクトリには必ずルートレイアウトを含める必要があります。- ルートレイアウトは
<html>と<body>タグを定義する必要があります(Next.jsは自動的に作成しません) - ルートレイアウトは
pages/_app.tsxとpages/_document.tsxファイルを置き換えます。 - レイアウトファイルには
.js、.jsx、.tsx拡張子を使用できます。
<head>HTML要素を管理するには、組み込みのSEOサポートを使用できます:
import type { Metadata } from 'next'
export const metadata: Metadata = {
title: 'Home',
description: 'Welcome to Next.js',
}export const metadata = {
title: 'Home',
description: 'Welcome to Next.js',
}_document.jsと_app.jsの移行
既存の_appまたは_documentファイルがある場合、その内容(例: グローバルスタイル)をルートレイアウト(app/layout.tsx)にコピーできます。app/layout.tsxのスタイルはpages/*には適用されません。pages/*ルートが壊れないように、移行中は_app/_documentを保持してください。完全に移行したら安全に削除できます。
React Contextプロバイダーを使用している場合、それらはクライアントコンポーネントに移動する必要があります。
getLayout()パターンからレイアウトへの移行(オプション)
Next.jsでは、pagesディレクトリでページごとのレイアウトを実現するためにPageコンポーネントにプロパティを追加することを推奨していました。このパターンは、appディレクトリでのネストされたレイアウトのネイティブサポートに置き換えることができます。
移行前後の例を見る
移行前
export default function DashboardLayout({ children }) {
return (
<div>
<h2>My Dashboard</h2>
{children}
</div>
)
}import DashboardLayout from '../components/DashboardLayout'
export default function Page() {
return <p>My Page</p>
}
Page.getLayout = function getLayout(page) {
return <DashboardLayout>{page}</DashboardLayout>
}移行後
-
pages/dashboard/index.jsからPage.getLayoutプロパティを削除し、ページの移行手順に従ってappディレクトリに移行します。app/dashboard/page.js export default function Page() { return <p>My Page</p> } -
DashboardLayoutの内容を新しいクライアントコンポーネントに移動し、pagesディレクトリの動作を維持します。app/dashboard/DashboardLayout.js 'use client' // このディレクティブはファイルの先頭(インポートの前)に記述 // これはクライアントコンポーネント export default function DashboardLayout({ children }) { return ( <div> <h2>My Dashboard</h2> {children} </div> ) } -
DashboardLayoutをappディレクトリ内の新しいlayout.jsファイルにインポートします。app/dashboard/layout.js import DashboardLayout from './DashboardLayout' // これはサーバーコンポーネント export default function Layout({ children }) { return <DashboardLayout>{children}</DashboardLayout> } -
クライアントに送信するコンポーネントJavaScriptの量を減らすため、
DashboardLayout.js(クライアントコンポーネント)の非インタラクティブ部分をlayout.js(サーバーコンポーネント)に段階的に移動できます。
ステップ3: next/headの移行
pagesディレクトリでは、next/headReactコンポーネントを使用してtitleやmetaなどの<head>HTML要素を管理していました。appディレクトリでは、next/headは新しい組み込みSEOサポートに置き換えられました。
移行前:
import Head from 'next/head'
export default function Page() {
return (
<>
<Head>
<title>My page title</title>
</Head>
</>
)
}import Head from 'next/head'
export default function Page() {
return (
<>
<Head>
<title>My page title</title>
</Head>
</>
)
}移行後:
import type { Metadata } from 'next'
export const metadata: Metadata = {
title: 'My Page Title',
}
export default function Page() {
return '...'
}export const metadata = {
title: 'My Page Title',
}
export default function Page() {
return '...'
}ステップ4: ページの移行
appディレクトリのページはデフォルトでサーバーコンポーネント (Server Components)です。これはクライアントコンポーネント (Client Components)であるpagesディレクトリのページとは異なります。appではデータフェッチング (Data Fetching)が変更されています。getServerSideProps、getStaticProps、getInitialPropsはよりシンプルなAPIに置き換えられました。appディレクトリでは、ルートを定義するためにネストされたフォルダを使用し、特別なpage.jsファイルを使用してルートセグメントを公開します。-
pagesディレクトリappディレクトリルート index.jspage.js/about.jsabout/page.js/aboutblog/[slug].jsblog/[slug]/page.js/blog/post-1
ページの移行は以下の2つの主要なステップに分けることを推奨します:
- ステップ1: デフォルトでエクスポートされているページコンポーネントを新しいクライアントコンポーネントに移動します。
- ステップ2: 新しいクライアントコンポーネントを
appディレクトリ内の新しいpage.jsファイルにインポートします。
補足: これは
pagesディレクトリとの動作が最も類似しているため、最も簡単な移行パスです。
ステップ1: 新しいクライアントコンポーネントを作成
appディレクトリ内に新しいファイル(例:app/home-page.tsxなど)を作成し、クライアントコンポーネントをエクスポートします。クライアントコンポーネントを定義するには、ファイルの先頭(インポートの前)に'use client'ディレクティブを追加します。- Pages Routerと同様に、初期ページロード時にクライアントコンポーネントを静的HTMLにプリレンダリングする最適化ステップがあります。
pages/index.jsからデフォルトでエクスポートされているページコンポーネントをapp/home-page.tsxに移動します。
'use client'
// これはクライアントコンポーネントです(`pages`ディレクトリのコンポーネントと同じ)
// データをpropsとして受け取り、状態やエフェクトにアクセスでき、
// 初期ページロード時にサーバーでプリレンダリングされます。
export default function HomePage({ recentPosts }) {
return (
<div>
{recentPosts.map((post) => (
<div key={post.id}>{post.title}</div>
))}
</div>
)
}'use client'
// これはクライアントコンポーネントです。データをpropsとして受け取り、
// `pages`ディレクトリのページコンポーネントと同様に状態やエフェクトにアクセスできます。
export default function HomePage({ recentPosts }) {
return (
<div>
{recentPosts.map((post) => (
<div key={post.id}>{post.title}</div>
))}
</div>
)
}ステップ2: 新しいページを作成
-
appディレクトリ内に新しいapp/page.tsxファイルを作成します。これはデフォルトでサーバーコンポーネントです。 -
home-page.tsxクライアントコンポーネントをページにインポートします。 -
pages/index.jsでデータをフェッチしていた場合、データフェッチングロジックを新しいデータフェッチングAPIを使用してサーバーコンポーネントに直接移動します。詳細はデータフェッチングアップグレードガイドを参照してください。// クライアントコンポーネントをインポート import HomePage from './home-page' async function getPosts() { const res = await fetch('https://...') const posts = await res.json() return posts } export default async function Page() { // サーバーコンポーネントで直接データをフェッチ const recentPosts = await getPosts() // フェッチしたデータをクライアントコンポーネントに渡す return <HomePage recentPosts={recentPosts} /> }// クライアントコンポーネントをインポート import HomePage from './home-page' async function getPosts() { const res = await fetch('https://...') const posts = await res.json() return posts } export default async function Page() { // サーバーコンポーネントで直接データをフェッチ const recentPosts = await getPosts() // フェッチしたデータをクライアントコンポーネントに渡す return <HomePage recentPosts={recentPosts} /> } -
以前のページで
useRouterを使用していた場合、新しいルーティングフックに更新する必要があります。詳細を確認。 -
開発サーバーを起動し、
http://localhost:3000にアクセスします。既存のインデックスルートがappディレクトリを通じて提供されているはずです。
ステップ5: ルーティングフックの移行
appディレクトリの新しい動作をサポートするために、新しいルーターが追加されました。
appでは、next/navigationからインポートされる3つの新しいフックを使用する必要があります:useRouter()、usePathname()、useSearchParams()。
- 新しい
useRouterフックはnext/navigationからインポートされ、next/routerからインポートされるpagesのuseRouterフックとは異なる動作をします。next/routerからインポートされるuseRouterフックはappディレクトリではサポートされていませんが、pagesディレクトリでは引き続き使用できます。
- 新しい
useRouterはpathname文字列を返しません。代わりに別のusePathnameフックを使用してください。 - 新しい
useRouterはqueryオブジェクトを返しません。検索パラメータと動的ルートパラメータは分離されました。代わりにuseSearchParamsとuseParamsフックを使用してください。 useSearchParamsとusePathnameを一緒に使用してページ変更を監視できます。詳細はルーターイベントセクションを参照してください。- これらの新しいフックはクライアントコンポーネントでのみサポートされています。サーバーコンポーネントでは使用できません。
'use client'
import { useRouter, usePathname, useSearchParams } from 'next/navigation'
export default function ExampleClientComponent() {
const router = useRouter()
const pathname = usePathname()
const searchParams = useSearchParams()
// ...
}'use client'
import { useRouter, usePathname, useSearchParams } from 'next/navigation'
export default function ExampleClientComponent() {
const router = useRouter()
const pathname = usePathname()
const searchParams = useSearchParams()
// ...
}さらに、新しいuseRouterフックには以下の変更があります:
isFallbackは削除されました。fallbackは置き換えられました。locale、locales、defaultLocales、domainLocalesの値は削除されました。appディレクトリではNext.jsの組み込みi18n機能は不要になったためです。i18nについて詳しく学ぶ。basePathは削除されました。代替手段はuseRouterの一部にはなりません。まだ実装されていません。asPathは削除されました。新しいルーターではasの概念が削除されたためです。isReadyは削除されました。不要になったためです。静的レンダリング (Static Rendering)中、useSearchParams()フックを使用するコンポーネントはプリレンダリングステップをスキップし、代わりに実行時にクライアントでレンダリングされます。routeは削除されました。代わりにusePathnameまたはuseSelectedLayoutSegments()を使用してください。
pagesとapp間でのコンポーネント共有
pagesとappルーター間でコンポーネントを互換性を持たせるには、next/compat/routerのuseRouterフックを参照してください。
これはpagesディレクトリのuseRouterフックですが、ルーター間でコンポーネントを共有する際に使用することを意図しています。appルーターでのみ使用する準備が整ったら、新しいnext/navigationのuseRouterに更新してください。
ステップ6: データフェッチングメソッドの移行
pagesディレクトリでは、ページのデータをフェッチするためにgetServerSidePropsとgetStaticPropsが使用されます。appディレクトリ内では、これらの以前のデータフェッチング関数は、fetch()と非同期Reactサーバーコンポーネントを基盤としたよりシンプルなAPIに置き換えられました。
export default async function Page() {
// このリクエストは手動で無効化されるまでキャッシュされます。
// `getStaticProps`と同様です。
// `force-cache`はデフォルトで省略可能です。
const staticData = await fetch(`https://...`, { cache: 'force-cache' })
// このリクエストは毎回再フェッチされます。
// `getServerSideProps`と同様です。
const dynamicData = await fetch(`https://...`, { cache: 'no-store' })
// このリクエストは10秒間キャッシュされます。
// `revalidate`オプション付きの`getStaticProps`と同様です。
const revalidatedData = await fetch(`https://...`, {
next: { revalidate: 10 },
})
return <div>...</div>
}export default async function Page() {
// このリクエストは手動で無効化されるまでキャッシュされます。
// `getStaticProps`と同様です。
// `force-cache`はデフォルトで省略可能です。
const staticData = await fetch(`https://...`, { cache: 'force-cache' })
// このリクエストは毎回再フェッチされます。
// `getServerSideProps`と同様です。
const dynamicData = await fetch(`https://...`, { cache: 'no-store' })
// このリクエストは10秒間キャッシュされます。
// `revalidate`オプション付きの`getStaticProps`と同様です。
const revalidatedData = await fetch(`https://...`, {
next: { revalidate: 10 },
})
return <div>...</div>
}サーバーサイドレンダリング (getServerSideProps)
pagesディレクトリでは、getServerSidePropsを使用してサーバー上でデータをフェッチし、ファイル内のデフォルトでエクスポートされるReactコンポーネントにpropsを渡します。ページの初期HTMLはサーバーからプリレンダリングされ、その後ブラウザでページを「ハイドレート」(インタラクティブに)します。
// `pages`ディレクトリ
export async function getServerSideProps() {
const res = await fetch(`https://...`)
const projects = await res.json()
return { props: { projects } }
}
export default function Dashboard({ projects }) {
return (
<ul>
{projects.map((project) => (
<li key={project.id}>{project.name}</li>
))}
</ul>
)
}App Routerでは、サーバーコンポーネント (Server Components)内でデータフェッチングを配置できます。これにより、クライアントに送信するJavaScriptを減らしながら、サーバーからのレンダリングされたHTMLを維持できます。
cacheオプションをno-storeに設定することで、フェッチしたデータを決してキャッシュしないように指示できます。これはpagesディレクトリのgetServerSidePropsと同様です。
// `app`ディレクトリ
// この関数は任意の名前を付けられます
async function getProjects() {
const res = await fetch(`https://...`, { cache: 'no-store' })
const projects = await res.json()
return projects
}
export default async function Dashboard() {
const projects = await getProjects()
return (
<ul>
{projects.map((project) => (
<li key={project.id}>{project.name}</li>
))}
</ul>
)
}// `app`ディレクトリ
// この関数は任意の名前を付けられます
async function getProjects() {
const res = await fetch(`https://...`, { cache: 'no-store' })
const projects = await res.json()
return projects
}
export default async function Dashboard() {
const projects = await getProjects()
return (
<ul>
{projects.map((project) => (
<li key={project.id}>{project.name}</li>
))}
</ul>
)
}リクエストオブジェクトへのアクセス
pagesディレクトリでは、Node.js HTTP APIに基づいてリクエストベースのデータを取得できます。
例えば、getServerSidePropsからreqオブジェクトを取得し、それを使用してリクエストのクッキーやヘッダーを取得できます。
// `pages`ディレクトリ
export async function getServerSideProps({ req, query }) {
const authHeader = req.getHeaders()['authorization'];
const theme = req.cookies['theme'];
return { props: { ... }}
}
export default function Page(props) {
return ...
}appディレクトリでは、リクエストデータを取得するための新しい読み取り専用関数が公開されています:
headers: Web Headers APIに基づいており、サーバーコンポーネント (Server Components)内でリクエストヘッダーを取得するために使用できます。cookies: Web Cookies APIに基づいており、サーバーコンポーネント (Server Components)内でクッキーを取得するために使用できます。
// `app`ディレクトリ
import { cookies, headers } from 'next/headers'
async function getData() {
const authHeader = (await headers()).get('authorization')
return '...'
}
export default async function Page() {
// サーバーコンポーネント内で直接、またはデータフェッチング関数内で
// `cookies`または`headers`を使用できます
const theme = (await cookies()).get('theme')
const data = await getData()
return '...'
}// `app`ディレクトリ
import { cookies, headers } from 'next/headers'
async function getData() {
const authHeader = (await headers()).get('authorization')
return '...'
}
export default async function Page() {
// サーバーコンポーネント内で直接、またはデータフェッチング関数内で
// `cookies`または`headers`を使用できます
const theme = (await cookies()).get('theme')
const data = await getData()
return '...'
}静的サイト生成 (getStaticProps)
pagesディレクトリでは、getStaticProps関数を使用してビルド時にページをプリレンダリングします。この関数を使用して外部APIやデータベースからデータをフェッチし、ビルド時に生成されるページ全体にこのデータを渡すことができます。
// `pages`ディレクトリ
export async function getStaticProps() {
const res = await fetch(`https://...`)
const projects = await res.json()
return { props: { projects } }
}
export default function Index({ projects }) {
return projects.map((project) => <div>{project.name}</div>)
}appディレクトリでは、fetch()を使用したデータフェッチングはデフォルトでcache: 'force-cache'になり、リクエストデータを手動で無効化するまでキャッシュします。これはpagesディレクトリのgetStaticPropsと同様です。
// `app`ディレクトリ
// この関数は任意の名前を付けられます
async function getProjects() {
const res = await fetch(`https://...`)
const projects = await res.json()
return projects
}
export default async function Index() {
const projects = await getProjects()
return projects.map((project) => <div>{project.name}</div>)
}ダイナミックパス (getStaticPaths)
pages ディレクトリでは、getStaticPaths 関数を使用してビルド時に事前レンダリングするダイナミックパスを定義します。
// `pages` ディレクトリ
import PostLayout from '@/components/post-layout'
export async function getStaticPaths() {
return {
paths: [{ params: { id: '1' } }, { params: { id: '2' } }],
}
}
export async function getStaticProps({ params }) {
const res = await fetch(`https://.../posts/${params.id}`)
const post = await res.json()
return { props: { post } }
}
export default function Post({ post }) {
return <PostLayout post={post} />
}app ディレクトリでは、getStaticPaths は generateStaticParams に置き換えられます。
generateStaticParams は getStaticPaths と同様の動作をしますが、ルートパラメータを返すための簡素化されたAPIを提供し、レイアウト内で使用できます。generateStaticParams の戻り値の形式は、ネストされた param オブジェクトの配列や解決済みパスの文字列ではなく、セグメントの配列です。
// `app` ディレクトリ
import PostLayout from '@/components/post-layout'
export async function generateStaticParams() {
return [{ id: '1' }, { id: '2' }]
}
async function getPost(params) {
const res = await fetch(`https://.../posts/${(await params).id}`)
const post = await res.json()
return post
}
export default async function Post({ params }) {
const post = await getPost(params)
return <PostLayout post={post} />
}app ディレクトリの新しいモデルでは、getStaticPaths よりも generateStaticParams という名前が適切です。get プレフィックスはより記述的な generate に置き換えられ、getStaticProps や getServerSideProps が不要になった現在では単独で適切です。Paths サフィックスは Params に置き換えられ、複数のダイナミックセグメントを持つネストされたルーティングにより適しています。
fallback の置き換え
pages ディレクトリでは、getStaticPaths から返される fallback プロパティを使用して、ビルド時に事前レンダリングされていないページの動作を定義します。このプロパティは、ページ生成中にフォールバックページを表示する true、404ページを表示する false、またはリクエスト時にページを生成する 'blocking' に設定できます。
// `pages` ディレクトリ
export async function getStaticPaths() {
return {
paths: [],
fallback: 'blocking'
};
}
export async function getStaticProps({ params }) {
...
}
export default function Post({ post }) {
return ...
}app ディレクトリでは、config.dynamicParams プロパティ が generateStaticParams に含まれていないパラメータの処理方法を制御します:
true: (デフォルト)generateStaticParamsに含まれていないダイナミックセグメントはオンデマンドで生成されます。false:generateStaticParamsに含まれていないダイナミックセグメントは404を返します。
これは pages ディレクトリの getStaticPaths の fallback: true | false | 'blocking' オプションを置き換えます。'blocking' オプションは dynamicParams に含まれていません。ストリーミングでは 'blocking' と true の違いがほとんどないためです。
// `app` ディレクトリ
export const dynamicParams = true;
export async function generateStaticParams() {
return [...]
}
async function getPost(params) {
...
}
export default async function Post({ params }) {
const post = await getPost(params);
return ...
}dynamicParams が true (デフォルト) に設定されている場合、生成されていないルートセグメントがリクエストされると、サーバーでレンダリングされキャッシュされます。
インクリメンタル静的再生成 (getStaticProps の revalidate)
pages ディレクトリでは、getStaticProps 関数に revalidate フィールドを追加することで、一定時間後にページを自動的に再生成できます。
// `pages` ディレクトリ
export async function getStaticProps() {
const res = await fetch(`https://.../posts`)
const posts = await res.json()
return {
props: { posts },
revalidate: 60,
}
}
export default function Index({ posts }) {
return (
<Layout>
<PostList posts={posts} />
</Layout>
)
}app ディレクトリでは、fetch() を使用したデータ取得に revalidate を使用でき、指定された秒数間リクエストをキャッシュします。
// `app` ディレクトリ
async function getPosts() {
const res = await fetch(`https://.../posts`, { next: { revalidate: 60 } })
const data = await res.json()
return data.posts
}
export default async function PostList() {
const posts = await getPosts()
return posts.map((post) => <div>{post.name}</div>)
}API ルート
API ルートは pages/api ディレクトリで変更なく引き続き動作します。ただし、app ディレクトリでは ルートハンドラ に置き換えられています。
ルートハンドラを使用すると、Web の Request と Response API を使用して、特定のルートのカスタムリクエストハンドラを作成できます。
export async function GET(request: Request) {}export async function GET(request) {}知っておくと良いこと: 以前にクライアントから外部APIを呼び出すためにAPIルートを使用していた場合、サーバーコンポーネント を使用して安全にデータを取得できるようになりました。データ取得 について詳しく学べます。
シングルページアプリケーション (SPA)
同時にシングルページアプリケーション (SPA) から Next.js に移行している場合は、ドキュメント を参照して詳細を確認してください。
ステップ7: スタイリング
pages ディレクトリでは、グローバルスタイルシートは pages/_app.js に限定されていました。app ディレクトリではこの制限が解除され、グローバルスタイルは任意のレイアウト、ページ、またはコンポーネントに追加できます。
Tailwind CSS
Tailwind CSS を使用している場合、tailwind.config.js ファイルに app ディレクトリを追加する必要があります:
module.exports = {
content: [
'./app/**/*.{js,ts,jsx,tsx,mdx}', // <-- この行を追加
'./pages/**/*.{js,ts,jsx,tsx,mdx}',
'./components/**/*.{js,ts,jsx,tsx,mdx}',
],
}また、app/layout.js ファイルでグローバルスタイルをインポートする必要があります:
import '../styles/globals.css'
export default function RootLayout({ children }) {
return (
<html lang="en">
<body>{children}</body>
</html>
)
}Tailwind CSS でのスタイリング について詳しく学べます
App Router と Pages Router の併用
異なる Next.js ルーターによって提供されるルート間をナビゲートする場合、ハードナビゲーションが発生します。next/link による自動リンクプリフェッチはルーター間では機能しません。
代わりに、App Router と Pages Router 間のナビゲーションを 最適化 して、プリフェッチと高速なページ遷移を維持できます。詳細を学ぶ
コードモッド
Next.js は、機能が非推奨になった際にコードベースをアップグレードするためのコードモッド変換を提供します。詳細は コードモッド を参照してください。