App Router Incremental Adoption Guide
このガイドでは、以下のことを行う方法を説明します:
- 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
Good to know: ESLintの変更を反映させるために、VS Codeで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の使用を必要としません。pages
を引き続き使用し、両方のディレクトリで動作する新機能、例えば更新されたImageコンポーネント、Linkコンポーネント、Scriptコンポーネント、およびフォント最適化を利用できます。
<Image/>
コンポーネント
Next.js 12では、next/future/image
という一時的なインポートを使用してImageコンポーネントの新しい改善が導入されました。これらの改善には、クライアントサイドのJavaScriptの削減、画像の拡張とスタイルの簡素化、アクセシビリティの向上、ネイティブブラウザの遅延読み込みが含まれていました。
バージョン13では、この新しい動作がnext/image
のデフォルトになりました。
新しいImageコンポーネントへの移行を支援するために、2つのコードモッドがあります:
next-image-to-legacy-image
codemod:next/image
インポートをnext/legacy/image
に安全かつ自動的にリネームします。既存のコンポーネントは同じ動作を維持します。next-image-experimental
codemod:インラインスタイルを危険に追加し、未使用のpropsを削除します。これにより、既存のコンポーネントの動作が新しいデフォルトに一致するように変更されます。このコードモッドを使用するには、最初にnext-image-to-legacy-image
コードモッドを実行する必要があります。
<Link>
コンポーネント
<Link>
コンポーネントは、子として手動で<a>
タグを追加する必要がなくなりました。この動作はバージョン12.2で実験的なオプションとして追加され、現在はデフォルトです。Next.js 13では、<Link>
は常に<a>
をレンダリングし、propsを基になるタグに転送できるようにします。
例:
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
codemodを使用できます。
<Script>
コンポーネント
next/script
の動作は、pages
とapp
の両方をサポートするように更新されましたが、スムーズな移行を確保するためにいくつかの変更が必要です:
- 以前に
_document.js
に含めていたbeforeInteractive
スクリプトをroot レイアウトファイル(app/layout.tsx
)に移動します。 - 実験的な
worker
戦略はまだapp
で動作しないため、この戦略で指定されたスクリプトは削除するか、別の戦略(例:lazyOnload
)を使用するように変更する必要があります。 onLoad
、onReady
、およびonError
ハンドラはServer Componentsでは動作しないため、Client Componentに移動するか、完全に削除してください。
フォント最適化
以前は、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への移行は、Next.jsが基盤として構築しているReactの機能(Server Components、Suspenseなど)を初めて使用することを意味するかもしれません。新しい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
root レイアウトに置き換えられました。詳細はこちら。pages/_error.js
は、より細かいerror.js
特別なファイルに置き換えられました。詳細はこちら。pages/404.js
はnot-found.js
ファイルに置き換えられました。pages/api/*
APIルートは、route.js
(Route Handler)特別なファイルに置き換えられました。
ステップ1:app
ディレクトリの作成
最新のNext.jsバージョンに更新します(13.4以上が必要です):
npm install next@latest
次に、プロジェクトのルート(またはsrc/
ディレクトリ)に新しいapp
ディレクトリを作成します。
ステップ2:Root レイアウトの作成
app
ディレクトリ内に新しいapp/layout.tsx
ファイルを作成します。これは、app
内のすべてのルートに適用されるRoot レイアウトです。
- TypeScript
- JavaScript
export default function RootLayout({
// レイアウトはchildren propを受け入れる必要があります。
// これはネストされたレイアウトやページで埋められます
children,
}: {
children: React.ReactNode
}) {
return (
<html lang="en">
<body>{children}</body>
</html>
)
}
export default function RootLayout({
// レイアウトはchildren propを受け入れる必要があります。
// これはネストされたレイアウトやページで埋められます
children,
}) {
return (
<html lang="en">
<body>{children}</body>
</html>
)
}
app
ディレクトリには必ずroot レイアウトが含まれている必要があります。- root レイアウトは
<html>
および<body>
タグを定義する必要があります。Next.jsは自動的にこれらを作成しません。 - root レイアウトは
pages/_app.tsx
およびpages/_document.tsx
ファイルを置き換えます。 - レイアウトファイルには
.js
、.jsx
、または.tsx
の拡張子を使用できます。
<head>
HTML要素を管理するには、組み込みのSEOサポートを使用できます:
- TypeScript
- JavaScript
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
ファイルがある場合、その内容(例:グローバルスタイル)をroot レイアウト(app/layout.tsx
)にコピーできます。app/layout.tsx
のスタイルはpages/*
には適用されません。移行中に_app
/_document
を保持して、pages/*
ルートが壊れないようにする必要があります。完全に移行が完了したら、それらを安全に削除できます。
React Contextプロバイダーを使用している場合、それらはClient Componentに移動する必要があります。
getLayout()
パターンをレイアウトに移行する(オプション)
Next.jsは、pages
ディレクトリでページごとのレイアウトを実現するためにページコンポーネントにプロパティを追加することを推奨していました。このパターンは、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.jsexport default function Page() {
return <p>My Page</p>
} -
DashboardLayout
の内容を新しいClient Componentに移動して、pages
ディレクトリの動作を保持します。app/dashboard/DashboardLayout.js'use client' // このディレクティブはファイルの先頭、インポートの前に配置する必要があります。
// これはClient Componentです
export default function DashboardLayout({ children }) {
return (
<div>
<h2>My Dashboard</h2>
{children}
</div>
)
} -
DashboardLayout
をapp
ディレクトリ内の新しいlayout.js
ファイルにインポートします。app/dashboard/layout.jsimport DashboardLayout from './DashboardLayout'
// これはServer Componentです
export default function Layout({ children }) {
return <DashboardLayout>{children}</DashboardLayout>
} -
DashboardLayout.js
(Client Component)の非インタラクティブな部分をlayout.js
(Server Component)に段階的に移動して、クライアントに送信するコンポーネントJavaScriptの量を減らすことができます。
ステップ3:next/head
の移行
pages
ディレクトリでは、next/head
Reactコンポーネントを使用してtitle
やmeta
などの<head>
HTML要素を管理します。app
ディレクトリでは、next/head
は新しい組み込みのSEOサポートに置き換えられます。
前:
- TypeScript
- JavaScript
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>
</>
)
}
後:
- TypeScript
- JavaScript
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です。これは、pages
ディレクトリのページがClient Componentsであるのとは異なります。app
でのデータフェッチは変更されました。getServerSideProps
、getStaticProps
、およびgetInitialProps
は、よりシンプルなAPIに置き換えられました。app
ディレクトリは、ネストされたフォルダを使用してルートを定義し、特別な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:デフォルトでエクスポートされたページコンポーネントを新しいClient Componentに移動します。
- ステップ2:新しいClient Componentを
app
ディレクトリ内の新しいpage.js
ファイルにインポートします。
Good to know: これは
pages
ディレクトリに最も類似した動作を持つため、最も簡単な移行パスです。
ステップ1:新しいClient Componentを作成する
app
ディレクトリ内に新しいファイル(例:app/home-page.tsx
など)を作成し、Client Componentをエクスポートします。Client Componentsを定義するには、ファイルの先頭に'use client'
ディレクティブを追加します(インポートの前)。- Pages Routerと同様に、初回ページロード時にClient Componentsを静的HTMLにプリレンダリングする最適化ステップがあります。
pages/index.js
からデフォルトでエクスポートされたページコンポーネントをapp/home-page.tsx
に移動します。
- TypeScript
- JavaScript
'use client'
// これはClient Componentです(`pages`ディレクトリのコンポーネントと同じ)
// データをpropsとして受け取り、状態とエフェクトにアクセスでき、
// 初回ページロード時にサーバーでプリレンダリングされます。
export default function HomePage({ recentPosts }) {
return (
<div>
{recentPosts.map((post) => (
<div key={post.id}>{post.title}</div>
))}
</div>
)
}
'use client'
// これはClient Componentです。データを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
ファイルを作成します。これはデフォルトでServer Componentです。home-page.tsx
Client Componentをページにインポートします。pages/index.js
でデータをフェッチしていた場合、新しいデータフェッチAPIを使用してServer Component内にデータフェッチロジックを直接移動します。詳細については、データフェッチのアップグレードガイドを参照してください。
- TypeScript
- JavaScript
// Client Componentをインポートします
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() {
// Server Component内でデータを直接フェッチします
const recentPosts = await getPosts()
// フェッチしたデータをClient Componentに転送します
return <HomePage recentPosts={recentPosts} />
}
// Client Componentをインポートします
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() {
// Server Component内でデータを直接フェッチします
const recentPosts = await getPosts()
// フェッチしたデータをClient Componentに転送します
return <HomePage recentPosts={recentPosts} />
}
- 以前のページで
useRouter
を使用していた場合、新しいルーティングフックに更新する必要があります。詳細はこちら。 - 開発サーバーを起動し、
http://localhost:3000
にアクセスします。app
ディレクトリを通じて提供される既存のインデックスルートが表示されるはずです。
ステップ5:ルーティングフックの移行
新しいルーターがapp
ディレクトリの新しい動作をサポートするために追加されました。
app
では、next/navigation
からインポートされた3つの新しいフック:useRouter()
、usePathname()
、およびuseSearchParams()
を使用する必要があります。
- 新しい
useRouter
フックはnext/navigation
からインポートされ、pages
のuseRouter
フック(next/router
からインポート)とは異なる動作をします。next/router
からインポートされたuseRouter
フックはapp
ディレクトリではサポートされていませんが、pages
ディレクトリでは引き続き使用できます。
- 新しい
useRouter
はpathname
文字列を返しません。代わりに別のusePathname
フックを使用してください。 - 新しい
useRouter
はquery
オブジェクトを返しません。検索パラメータと動的ルートパラメータは別々になりました。代わりにuseSearchParams
とuseParams
フックを使用してください。 useSearchParams
とusePathname
を一緒に使用してページの変更を監視できます。詳細はルーターイベントセクションを参照してください。- これらの新しいフックはClient Componentsでのみサポートされています。Server Componentsでは使用できません。
- TypeScript
- JavaScript
'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
の値は削除されました。組み込みのi18n Next.js機能はapp
ディレクトリでは不要です。i18nについて詳しく学ぶ。basePath
は削除されました。代替案はuseRouter
の一部にはなりません。まだ実装されていません。asPath
は削除されました。新しいルーターからas
の概念が削除されたためです。isReady
は削除されました。もはや必要ありません。静的レンダリング中に、useSearchParams()
フックを使用するコンポーネントはプリレンダリングステップをスキップし、代わりにクライアントで実行時にレンダリングされます。route
は削除されました。usePathname
またはuseSelectedLayoutSegments()
が代替手段を提供します。
pages
とapp
間でのコンポーネントの共有
コンポーネントをpages
とapp
ルーター間で互換性を保つために、next/compat/router
からのuseRouter
フックを参照してください。
これはpages
ディレクトリからのuseRouter
フックですが、ルーター間でコンポーネントを共有する際に使用することを意図しています。app
ルーターでのみ使用する準備ができたら、新しいnext/navigation
からのuseRouter
に更新してください。
ステップ6:データフェッチメソッドの移行
pages
ディレクトリでは、getServerSideProps
とgetStaticProps
を使用してページのデータをフェッチします。app
ディレクトリ内では、これらの以前のデータフェッチ関数は、fetch()
とasync
React Server Componentsの上に構築されたよりシンプルなAPIに置き換えられました。
- TypeScript
- JavaScript
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を使用してReactコンポーネント内にデータフェッチを配置できます。これにより、クライアントに送信するJavaScriptの量を減らしながら、サーバーからレンダリングされたHTMLを維持できます。
cache
オプションをno-store
に設定することで、フェッチされたデータが決してキャッシュされないことを示すことができます。これはpages
ディレクトリのgetServerSideProps
に類似しています。
- TypeScript
- JavaScript
// `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
オブジェクトを取得し、リクエストのcookieやヘッダーを取得することができます。
// `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内でcookieを取得するために使用できます。
- TypeScript
- JavaScript
// `app`ディレクトリ
import { cookies, headers } from 'next/headers'
async function getData() {
const authHeader = (await headers()).get('authorization')
return '...'
}
export default async function Page() {
// `cookies`または`headers`をServer Components内で
// 直接またはデータフェッチ関数内で使用できます
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`をServer Components内で
// 直接またはデータフェッチ関数内で使用できます
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/${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
ディレクトリの新しいモデルにおいて、generateStaticParams
という名前を使用することはgetStaticPaths
よりも適切です。get
プレフィックスは、getStaticProps
やgetServerSideProps
がもはや必要ないため、より説明的なgenerate
に置き換えられました。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'
オプションを置き換えます。dynamicParams
にはfallback: 'blocking'
オプションは含まれていません。'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
with 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を使用して、特定のルートのカスタムリクエストハンドラを作成できます。
- TypeScript
- JavaScript
export async function GET(request: Request) {}
export async function GET(request) {}
Good to know: 以前にAPIルートを使用してクライアントから外部APIを呼び出していた場合、代わりにServer Componentsを使用してデータを安全にフェッチできます。データフェッチについて詳しく学ぶ。
シングルページアプリケーション
同時にシングルページアプリケーション(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>
)
}
コードモッド
Next.jsは、機能が廃止されたときにコードベースをアップグレードするのを支援するためのコードモッド変換を提供します。詳細はコードモッドを参照してください。