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
Good to know: 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の使用を必須としません。pages
を引き続き利用し、新しいImageコンポーネント、Linkコンポーネント、Scriptコンポーネント、およびフォント最適化など、両ディレクトリで機能する新機能を使用できます。
<Image/>
コンポーネント
Next.js 12では、一時的なインポートとしてImageコンポーネントの新しい改善を導入しました:next/future/image
。これらの改善には、クライアントサイドのJavaScriptの削減、画像の拡張とスタイルの容易化、アクセシビリティの向上、およびネイティブブラウザーの遅延読み込みが含まれていました。
バージョン13では、この新しい動作がnext/image
のデフォルトになりました。
新しいImageコンポーネントへの移行を支援するための2つのcodemodがあります:
next-image-to-legacy-image
codemod:next/image
インポートをnext/legacy/image
に安全かつ自動的にリネームします。既存のコンポーネントは同じ動作を維持します。next-image-experimental
codemod: インラインスタイルを危険な形で追加し、使用されていないpropsを削除します。このcodemodを使用するには、最初にnext-image-to-legacy-image
codemodを実行する必要があります。
<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
次に、プロジェクトのroot(またはsrc/
ディレクトリ)に新しいapp
ディレクトリを作成します。
ステップ2:Root レイアウトの作成
app
ディレクトリ内に新しいapp/layout.tsx
ファイルを作成します。これはapp
内のすべてのルートに適用されるroot レイアウトです。
- TypeScript
- JavaScript
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
ディレクトリにはroot レイアウトが必須です。- root レイアウトは、Next.jsが自動的に作成しないため、
<html>
と<body>
タグを定義する必要があります; - 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/*
には適用されません。pages/*
ルートが壊れないようにするため、移行中は_app
/_document
を保持する必要があります。完全に移行した場合、それらを安全に削除できます。
Reactコンテキストプロバイダーを使用している場合、それらをClient Componentに移動する必要があります。
getLayout()
パターンからレイアウトへの移行(任意)
Next.jsは、pages
ディレクトリでページごとのレイアウトを達成するため、ページコンポーネントへのプロパティ追加を推奨しました。このパターンは、app
ディレクトリ内のネストされたレイアウトのネイティブサポートに置き換えられます。
前後の例を確認する
Before
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>
}
After
-
pages/dashboard/index.js
からPage.getLayout
プロパティを削除し、app
ディレクトリへのページ移行手順に従います。app/dashboard/page.jsexport default function Page() {
return <p>My Page</p>
} -
DashboardLayout
の内容を、pages
ディレクトリの動作を保持するための新しいClient Componentとして移動します。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>
} -
コンポーネントJavaScriptをクライアントに送信する量を減らすために、
DashboardLayout.js
(Client Component)の非インタラクティブな部分をインクリメンタルにlayout.js
(Server Component)に移動できます。
ステップ3:next/head
の移行
pages
ディレクトリでは、next/head
Reactコンポーネントを使用して、title
やmeta
といった<head>
HTML要素を管理しますが、app
ディレクトリでは、next/head
は新しい組み込みのSEOサポートに置き換えられました。
Before:
- 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>
</>
)
}
After:
- 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を作成
- Client Componentをエクスポートする新しい別ファイルを
app
ディレクトリ内に作成します(例:app/home-page.tsx
など)。Client Componentsを定義するには、ファイルのトップに'use client'
ディレクティブを追加します(インポートの前)。- Pages Routerと同様に、初期ページロード時にClient Componentsを静的HTMLにプレンダリングする最適化ステップがあります。
pages/index.js
からapp/home-page.tsx
にデフォルトでエクスポートされたページコンポーネントを移動します。
- TypeScript
- JavaScript
'use client'
// これはClient Componentです(`pages`ディレクトリ内のコンポーネントと同じ)
// データをプロップとして受け取り、状態とエフェクトにアクセスでき
// 初期ページ読み込み時にサーバーでプリレンダリングされます。
export default function HomePage({ recentPosts }) {
return (
<div>
{recentPosts.map((post) => (
<div key={post.id}>{post.title}</div>
))}
</div>
)
}
'use client'
// これはClient Componentです。データをプロップとして受け取り、
// 状態とエフェクトにアクセスでき、`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
内でnext/router
からインポートされるuseRouter
フックとは異なる動作を持っています。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
の間でのコンポーネント共有
コンポーネントを両方のルーター間で互換性を保つには、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秒間キャッシュされるべきです。
// `getStaticProps`の`revalidate`オプションに類似。
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秒間キャッシュされるべきです。
// `getStaticProps`の`revalidate`オプションに類似。
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では、データの取得をReactコンポーネント内にコロケートできるServer Componentsを使用します。これにより、クライアントに送信する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
という名前を使用する方が適しています。getStaticProps
とgetServerSideProps
がもはや必要なくなったため、get
接頭辞はより記述的な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
の外部でparamsをどのように処理するかを制御します:
true
: (デフォルト)generateStaticParams
に含まれていない動的セグメントはオンデマンドで生成されます。false
:generateStaticParams
に含まれていない動的セグメントは404を返します。
これはpages
ディレクトリのgetStaticPaths
におけるfallback: true | false | 'blocking'
オプションを置き換えます。ストリーミングでは'blocking'
とtrue
の間の違いはわずかであるため、dynamicParams
にはfallback: 'blocking'
オプションは含まれていません。
// `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
関数を使用して、一定期間後に自動的にページを再生成します。
// `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 Handlerに置き換えられました。
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を使用してデータを安全に取得できます。データ取得についてもっと知る。
ステップ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>
)
}
Codemods
Next.jsは、機能が廃止されたときにコードベースをアップグレードするのに役立つCodemod変換を提供します。詳細はCodemodsを参照してください。