Pages Router から App Router への移行
このガイドでは次のことが学べます:
- Next.js アプリケーションをバージョン12からバージョン13に更新
pages
とapp
ディレクトリの両方で動作する機能のアップグレード- 既存アプリケーションを
pages
からapp
へ段階的に移行
アップグレード
Node.js バージョン
Node.js の最低バージョン要件は v18.17 になりました。詳細は Node.js ドキュメントをご覧ください。
Next.js バージョン
Next.js バージョン13に更新するには、お好みのパッケージマネージャーで次のコマンドを実行します:
npm install next@latest react@latest react-dom@latest
ESLint バージョン
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
と app
の両方で動作する新機能を引き続き使用できます。
<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 { 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/head
Reactコンポーネントを使用して 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 { 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)です。これはpages
ディレクトリのページがクライアントコンポーネント (Client Components)であるのとは異なります。- データフェッチング (Data fetching)は
app
ディレクトリで変更されています。getServerSideProps
、getStaticProps
、getInitialProps
はよりシンプルなAPIに置き換えられました。 app
ディレクトリでは、ルートを定義する (defining routes)ためにネストされたフォルダを使用し、特別なpage.js
ファイルを使ってルートセグメントを公開可能にします。-
pages
ディレクトリapp
ディレクトリルート index.js
page.js
/
about.js
about/page.js
/about
blog/[slug].js
blog/[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として受け取り、stateやeffectsにアクセス可能で、
// 初期ページロード時にサーバーでプリレンダリングされます。
export default function HomePage({ recentPosts }) {
return (
<div>
{recentPosts.map((post) => (
<div key={post.id}>{post.title}</div>
))}
</div>
)
}
'use client'
// これはクライアントコンポーネントです。データをpropsとして受け取り、
// `pages`ディレクトリのページコンポーネントと同様にstateやeffectsにアクセス可能です。
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
からインポートされ、pages
ディレクトリのnext/router
からインポートされるuseRouter
フックとは異なる動作をします。next/router
からインポートされるuseRouter
フックはapp
ディレクトリではサポートされていませんが、pages
ディレクトリでは引き続き使用できます。
- 新しい
useRouter
はpathname
文字列を返しません。代わりに別のusePathname
フックを使用してください。 - 新しい
useRouter
はquery
オブジェクトを返しません。代わりに別のuseSearchParams
フックを使用してください。 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()
フックを使用するコンポーネントはプリレンダリングステップをスキップし、代わりに実行時にクライアントでレンダリングされます。
ステップ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
ディレクトリでは、サーバーコンポーネント (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 = headers().get('authorization')
return '...'
}
export default async function Page() {
// サーバーコンポーネント内で直接、またはデータフェッチング関数内で
// `cookies()`や`headers()`を使用できます
const theme = cookies().get('theme')
const data = await getData()
return '...'
}
// `app`ディレクトリ
import { cookies, headers } from 'next/headers'
async function getData() {
const authHeader = headers().get('authorization')
return '...'
}
export default async function Page() {
// サーバーコンポーネント内で直接、またはデータフェッチング関数内で
// `cookies()`や`headers()`を使用できます
const theme = 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/${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
に設定すると、ページ生成中にフォールバックページを表示し、false
に設定すると404ページを表示し、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'
オプションを置き換えます。fallback: '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
ディレクトリでは Route Handlers に置き換えられています。
Route Handlers を使用すると、Web の Request および Response API を使用して、特定のルートのカスタムリクエストハンドラを作成できます。
export async function GET(request: Request) {}
export async function GET(request) {}
豆知識: 以前にクライアントから外部 API を呼び出すために API ルートを使用していた場合、Server Components を使用して安全にデータを取得できるようになりました。詳細は データ取得 をご覧ください。
ステップ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 でのスタイリング をご覧ください
コードモッド
Next.js は、機能が非推奨になったときにコードベースをアップグレードするためのコードモッド変換を提供しています。詳細は コードモッド をご覧ください。