Menu

Pages から App へ

このガイドは、以下の内容を支援します:

アップグレード

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 サーバーを再起動する必要がある場合があります。コマンドパレット(Mac では cmd+shift+p、Windows では ctrl+shift+p)を開き、ESLint: Restart ESLint Server を検索してください。

次のステップ

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

新機能のアップグレード

Next.js 13 は、新しい機能と規約を持つ新しい App 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 つの codemod があります:

  • next-image-to-legacy-image codemodnext/image のインポートを安全かつ自動的に next/legacy/image に名前変更します。既存のコンポーネントは同じ動作を維持します。
  • next-image-experimental codemod:インラインスタイルを危険に追加し、未使用の props を削除します。これにより、既存のコンポーネントの動作が新しいデフォルトに一致するように変更されます。この codemod を使用するには、最初に next-image-to-legacy-image codemod を実行する必要があります。

<Link> コンポーネントは、子として手動で <a> タグを追加する必要がなくなりました。この動作は version 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 の動作は 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 などの React 機能を Next.js がどのように構築しているかを初めて使用する機会かもしれません。特殊ファイルレイアウトなどの新しい 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 内のすべてのルートに適用されるルートレイアウトです。

app/layout.tsx
TypeScript
export default function RootLayout({
  // レイアウトは children プロパティを受け入れる必要があります。
  // これは、ネストされたレイアウトまたはページで埋められます
  children,
}: {
  children: React.ReactNode
}) {
  return (
    <html lang="en">
      <body>{children}</body>
    </html>
  )
}
  • app ディレクトリには必ずルートレイアウトを含める必要があります。
  • ルートレイアウトは、Next.jsが自動的に作成しないため、<html> および <body> タグを定義する必要があります。
  • ルートレイアウトは、pages/_app.tsx および pages/_document.tsx ファイルを置き換えます。
  • レイアウトファイルには .js.jsx、または .tsx 拡張子を使用できます。

<head> HTMLエレメントを管理するには、組み込みのSEOサポートを使用できます:

app/layout.tsx
TypeScript
import type { Metadata } from 'next'
 
export const metadata: 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 コンテキストプロバイダーを使用している場合は、クライアントコンポーネントに移動する必要があります。

getLayout() パターンをレイアウトに移行(オプション)

Next.jsでは、pages ディレクトリでページごとのレイアウトを実現するために、ページコンポーネントにプロパティを追加することをお勧めしていました。このパターンは、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>
      )
    }
  • app ディレクトリ内の新しい layout.js ファイルに DashboardLayout をインポートします。

    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サポートに置き換えられます。

変更前:

pages/index.tsx
TypeScript
import Head from 'next/head'
 
export default function Page() {
  return (
    <>
      <Head>
        <title>My page title</title>
      </Head>
    </>
  )
}

変更後:

app/page.tsx
TypeScript
import type { Metadata } from 'next'
 
export const metadata: Metadata = {
  title: 'My Page Title',
}
 
export default function Page() {
  return '...'
}

すべてのメタデータオプションを参照

ステップ 4: ページの移行

  • appディレクトリのページは、デフォルトでサーバーコンポーネントです。これは、ページがクライアントコンポーネントであるpagesディレクトリとは異なります。
  • appのデータフェッチが変更されました。getServerSidePropsgetStaticPropsgetInitialPropsは、よりシンプルなAPIに置き換えられました。
  • appディレクトリは、ネストされたフォルダを使用してルートを定義し、特別なpage.jsファイルでルートセグメントを公開します。
  • pagesディレクトリappディレクトリルート
    index.jspage.js/
    about.jsabout/page.js/about
    blog/[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に移動します。
app/home-page.tsx
TypeScript
'use client'
 
// これはクライアントコンポーネントです(`pages`ディレクトリのコンポーネントと同じ)
// propsとしてデータを受け取り、状態とエフェクトにアクセスでき、
// 初期ページ読み込み時にサーバー上でプレレンダリングされます。
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を使用して、サーバーコンポーネントに直接データフェッチのロジックを移動します。詳細はデータフェッチアップグレードガイドを参照してください。

    app/page.tsx
    TypeScript
    // クライアントコンポーネントをインポート
    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からインポートされ、pagesuseRouterフック(next/routerからインポート)とは異なる動作をします。
  • 新しいuseRouterpathname文字列を返しません。代わりに別のusePathnameフックを使用してください。
  • 新しいuseRouterqueryオブジェクトを返しません。検索パラメータと動的ルートパラメータは現在別々になっています。代わりにuseSearchParamsuseParamsフックを使用してください。
  • useSearchParamsusePathnameを一緒に使用して、ページ変更をリッスンできます。詳細はルーターイベントセクションを参照してください。
  • これらの新しいフックはクライアントコンポーネントでのみサポートされます。サーバーコンポーネントでは使用できません。
app/example-client-component.tsx
TypeScript
'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ディレクトリでは組み込みのi18n Next.js機能が不要になったためです。i18nの詳細はこちら
  • basePathは削除されました。代替はuseRouterの一部にはなりません。まだ実装されていません。
  • asPathは削除されました。新しいルーターからasの概念が削除されたためです。
  • isReadyは削除されました。もはや必要ありません。静的レンダリング中、useSearchParams()フックを使用するコンポーネントは、プリレンダリングステップをスキップし、代わりにランタイム時にクライアント上でレンダリングされます。
  • routeは削除されました。usePathnameまたはuseSelectedLayoutSegments()が代替を提供します。

useRouter()のAPIリファレンスを表示

pagesapp間でのコンポーネント共有

コンポーネントを pagesapp ルーター間で互換性を保つには、next/compat/router からの useRouter フックを参照してください。 これは pages ディレクトリからの useRouter フックですが、ルーター間でコンポーネントを共有する際に使用することを意図しています。app ルーターのみで使用する準備ができたら、next/navigation からの新しい useRouter に更新してください。

ステップ 6: データ取得メソッドの移行

pages ディレクトリは、ページのデータを取得するために getServerSidePropsgetStaticProps を使用します。app ディレクトリ内では、これらの以前のデータ取得関数は、fetch()async React サーバーコンポーネントに基づいたシンプルな API に置き換えられています。

app/page.tsx
TypeScript
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 コンポーネントにプロップを転送するために使用されます。ページの初期 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 Router では、サーバーコンポーネントを使用して、データ取得をReactコンポーネント内に配置できます。これにより、サーバーからレンダリングされたHTMLを維持しながら、クライアントに送信するJavaScriptを少なくすることができます。

cache オプションを no-store に設定することで、フェッチされたデータが決してキャッシュされないことを示すことができます。これは pages ディレクトリの getServerSideProps に類似しています。

app/dashboard/page.tsx
TypeScript
// `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/page.tsx
TypeScript
// `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/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} />
}

generateStaticParamsという名称は、appディレクトリの新しいモデルにおいて、getStaticPathsよりも適切です。getプレフィックスは、getStaticPropsgetServerSidePropsが不要になったため、より説明的なgenerateに置き換えられています。Pathsサフィックスは、複数の動的セグメントを持つネストされたルーティングに適したParamsに置き換えられています。

fallbackの置き換え

pagesディレクトリでは、getStaticPathsから返されるfallbackプロパティは、ビルド時にプリレンダリングされていないページの動作を定義するために使用されます。このプロパティは、ページの生成中にフォールバックページを表示するtrue、404ページを表示するfalse、またはリクエスト時にページを生成する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に含まれていない動的セグメントは、オンデマンドで生成されます。
  • falsegenerateStaticParamsに含まれていない動的セグメントは、404を返します。

これはpagesディレクトリのgetStaticPathsfallback: true | false | 'blocking'オプションを置き換えます。'blocking'trueの違いがストリーミングでほとんどないため、dynamicParamsにはfallback: 'blocking'オプションは含まれていません。

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

増分静的再生成(revalidate付きのgetStaticProps

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ディレクトリではルートハンドラに置き換えられています。

ルートハンドラを使用すると、Web RequestResponse APIを使用して、特定のルートのカスタムリクエストハンドラを作成できます。

app/api/route.ts
export async function GET(request: Request) {}
app/api/route.js
export async function GET(request) {}

補足:以前クライアントから外部APIを呼び出すためにAPIルートを使用していた場合、現在はサーバーコンポーネントを使用してデータを安全にフェッチできます。データフェッチについてさらに詳しく学べます。

ステップ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でのスタイリングについてさらに詳しく学べます

Codemods

Next.jsは、機能が非推奨になった際にコードベースをアップグレードするためのCodemodトランスフォーメーションを提供しています。詳細についてはCodemodsを参照してください。