Pages Router から App Router への移行

このガイドでは次のことが学べます:

アップグレード

Node.js バージョン

Node.js の最低バージョン要件は v18.17 になりました。詳細は Node.js ドキュメントをご覧ください。

Next.js バージョン

Next.js バージョン13に更新するには、お好みのパッケージマネージャーで次のコマンドを実行します:

Terminal
npm install next@latest react@latest react-dom@latest

ESLint バージョン

ESLint を使用している場合、ESLint のバージョンをアップグレードする必要があります:

Terminal
npm install -D eslint-config-next@latest

補足: VS Code でESLintの変更を反映させるには、ESLintサーバーを再起動する必要がある場合があります。コマンドパレット(Macでは cmd+shift+p、Windowsでは ctrl+shift+p)を開き、ESLint: Restart ESLint Server を検索してください。

次のステップ

更新後は、次のセクションを参照してください:

新機能のアップグレード

Next.js 13 では新しい App Router が導入され、新機能と規約が追加されました。新しい Router は app ディレクトリで利用可能で、pages ディレクトリと共存できます。

Next.js 13 へのアップグレードは、新しい App Router の使用を必須としません。更新された Image コンポーネントLink コンポーネントScript コンポーネントフォント最適化など、pagesapp の両方で動作する新機能を引き続き使用できます。

<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> コンポーネント は、子要素として <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 の動作が pagesapp の両方をサポートするように更新されましたが、スムーズな移行のためにいくつかの変更が必要です:

  • _document.js に含めていた beforeInteractive スクリプトは、ルートレイアウトファイル (app/layout.tsx) に移動してください。
  • 実験的な worker 戦略は app ではまだ機能せず、この戦略で指定されたスクリプトは削除するか、別の戦略(例: lazyOnload)を使用するように変更する必要があります。
  • onLoadonReadyonError ハンドラはサーバーコンポーネントでは機能しないため、クライアントコンポーネント に移動するか、完全に削除してください。

フォント最適化

以前、Next.js は フォントCSSのインライン化 によってフォントの最適化を支援していました。バージョン13では、優れたパフォーマンスとプライバシーを確保しながらフォント読み込みエクスペリエンスをカスタマイズできる新しい next/font モジュールが導入されました。next/fontpagesapp ディレクトリの両方でサポートされています。

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.jslayout.js です。
    • page.js を使用して、ルート固有のUIを定義します。
    • layout.js を使用して、複数のルートで共有されるUIを定義します。
    • 特殊ファイルには .js.jsx.tsx ファイル拡張子を使用できます。
  • コンポーネント、スタイル、テストなど、他のファイルを app ディレクトリ内に配置できます。詳細.
  • getServerSidePropsgetStaticProps などのデータ取得関数は、app 内の 新しいAPI に置き換えられました。getStaticPathsgenerateStaticParams に置き換えられました。
  • pages/_app.jspages/_document.js は、単一の app/layout.js ルートレイアウトに置き換えられました。詳細.
  • pages/_error.js は、より細かい error.js 特殊ファイルに置き換えられました。詳細.
  • pages/404.jsnot-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.tsxpages/_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 ディレクトリでの ネストされたレイアウト のネイティブサポートに置き換えることができます。

移行前後の例を見る

移行前

components/DashboardLayout.js
export default function DashboardLayout({ children }) {
  return (
    <div>
      <h2>My Dashboard</h2>
      {children}
    </div>
  )
}
pages/dashboard/index.js
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>
      )
    }
  • DashboardLayoutapp ディレクトリ内の新しい 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コンポーネントを使用して titlemeta などの <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: ページの移行

ページの移行は以下の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フックとは異なる動作をします。
  • 新しいuseRouterpathname文字列を返しません。代わりに別のusePathnameフックを使用してください。
  • 新しいuseRouterqueryオブジェクトを返しません。代わりに別のuseSearchParamsフックを使用してください。
  • useSearchParamsusePathnameを一緒に使用してページの変更を監視できます。詳細はルーターイベントセクションを参照してください。
  • これらの新しいフックはクライアントコンポーネントでのみサポートされています。サーバーコンポーネントでは使用できません。
'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置き換えられました
  • localelocalesdefaultLocalesdomainLocalesの値は削除されました。appディレクトリではNext.jsの組み込みi18n機能は不要になったためです。i18nについて詳しく学ぶ
  • basePathは削除されました。代替機能はuseRouterの一部ではなく、まだ実装されていません。
  • asPathは削除されました。新しいルーターではasの概念がなくなったためです。
  • isReadyは削除されました。もはや必要ないためです。静的レンダリング (static rendering)中に、useSearchParams()フックを使用するコンポーネントはプリレンダリングステップをスキップし、代わりに実行時にクライアントでレンダリングされます。

useRouter() APIリファレンスを確認

ステップ6: データフェッチングメソッドの移行

pagesディレクトリでは、ページのデータをフェッチするためにgetServerSidePropsgetStaticPropsが使用されます。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/dashboard.js
// `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/index.js
// `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ディレクトリでは、リクエストデータを取得するための新しい読み取り専用関数が公開されています:

// `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/index.js
// `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/page.js
// `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/posts/[id].js
// `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 ディレクトリでは、getStaticPathsgenerateStaticParams に置き換えられます。

generateStaticParamsgetStaticPaths と同様の動作をしますが、ルートパラメータを返すための簡素化された API を提供し、レイアウト 内で使用できます。generateStaticParams の戻り値の形式は、ネストされた param オブジェクトの配列や解決済みパスの文字列ではなく、セグメントの配列です。

app/posts/[id]/page.js
// `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 に置き換えられ、getStaticPropsgetServerSideProps が不要になった現在では単独で適切です。Paths サフィックスは、複数の動的セグメントを持つネストされたルーティングにより適した Params に置き換えられました。


fallback の置き換え

pages ディレクトリでは、getStaticPaths から返される fallback プロパティを使用して、ビルド時に事前レンダリングされないページの動作を定義します。このプロパティを true に設定すると、ページ生成中にフォールバックページを表示し、false に設定すると404ページを表示し、blocking に設定するとリクエスト時にページを生成します。

pages/posts/[id].js
// `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 ディレクトリの getStaticPathsfallback: true | false | 'blocking' オプションを置き換えます。fallback: 'blocking' オプションは dynamicParams に含まれていません。なぜなら、ストリーミングでは 'blocking'true の違いがほとんどないためです。

app/posts/[id]/page.js
// `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 ...
}

dynamicParamstrue (デフォルト) に設定すると、生成されていないルートセグメントがリクエストされた場合、サーバーでレンダリングされキャッシュされます。

インクリメンタル静的再生成 (getStaticPropsrevalidate)

pages ディレクトリでは、getStaticProps 関数に revalidate フィールドを追加することで、一定時間後にページを自動的に再生成できます。

pages/index.js
// `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/page.js
// `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 ディレクトリを追加する必要があります:

tailwind.config.js
module.exports = {
  content: [
    './app/**/*.{js,ts,jsx,tsx,mdx}', // <-- この行を追加
    './pages/**/*.{js,ts,jsx,tsx,mdx}',
    './components/**/*.{js,ts,jsx,tsx,mdx}',
  ],
}

また、app/layout.js ファイルでグローバルスタイルをインポートする必要があります:

app/layout.js
import '../styles/globals.css'

export default function RootLayout({ children }) {
  return (
    <html lang="en">
      <body>{children}</body>
    </html>
  )
}

詳細は Tailwind CSS でのスタイリング をご覧ください

コードモッド

Next.js は、機能が非推奨になったときにコードベースをアップグレードするためのコードモッド変換を提供しています。詳細は コードモッド をご覧ください。