メインコンテンツまでスキップ

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

Good to know: VS Code で ESLint の変更を有効にするには、ESLint サーバーを再起動する必要があるかもしれません。コマンドパレット (cmd+shift+p on Mac; ctrl+shift+p on Windows)を開き、ESLint: Restart ESLint Server と検索してください。

次のステップ

更新後は、次のステップに進んでください:

新しい機能のアップグレード

Next.js 13 は新しい特徴と規約を備えた新しい App Router を導入しました。新しい Router は app ディレクトリ内にあり、pages ディレクトリと共存します。

Next.js 13 へのアップグレードは、新しい App Router の使用を要求しません。pagesapp の両方のディレクトリで動作する新しい機能、例えば更新された Image コンポーネントLink コンポーネントScript コンポーネント、および フォントの最適化 を利用し続けることが可能です。

<Image/> コンポーネント

Next.js 12 は、next/future/image という一時的なインポートを利用して Image コンポーネントを改善しました。これには、クライアントサイドの JavaScript 量の削減、画像を拡張しスタイルを当てやすくなる方法、アクセシビリティの向上、ネイティブブラウザの遅延読み込みが含まれていました。

バージョン 13 では、この新しい動作が next/image のデフォルトになる。

新しい Image コンポーネントへの移行を支援するために、二つのコード変換ツールがあります:

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

<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 コード変換ツール を使用してください。

<Script> コンポーネント

next/script の動作は pagesapp の両方をサポートするように更新されましたが、スムーズな移行を確実にするためにいくつかの変更を加える必要があります:

  • beforeInteractive スクリプトをこれまで _document.js に含めていた場合、app/layout.tsx の root レイアウトファイルに移動します。
  • 実験的な worker 戦略は app ではまだ機能しないので、この戦略で記載されたスクリプトを削除するか、別の戦略(例:lazyOnload)を使うように変更する必要があります。
  • onLoadonReady、および onError ハンドラーは Server Components では動作しないので、Client Component に移動するか、完全に削除してください。

フォント最適化

以前は、Next.js は フォントCSSをインライン化する ことでフォントを最適化しました。バージョン 13 では、新しい next/font モジュールが導入され、フォント読み込み体験をカスタマイズする能力を提供しながらも、優れたパフォーマンスとプライバシーを保証します。next/font は、pagesapp の両方のディレクトリでサポートされています。

CSSのインライン化pages では動作しますが、app では動作しません。代わりに next/font を使用してください。

フォント最適化の使用方法については、フォントの最適化ページを参照してください。

pages から app への移行

🎥 視聴: App Routerの段階的採用方法を学びましょうYouTube(16分)

App Router への移行では、Server Components や 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 root レイアウトに置き換えられました。 詳細はこちら
  • pages/_error.js はより細かい粒度の error.js 特別ファイルに置き換えられました。 詳細はこちら
  • pages/404.jsnot-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 レイアウト です。

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

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

app/layout.tsx
import type { Metadata } from 'next'

export const metadata: 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 コンテクストプロバイダを使用している場合は、Client Component に移動する必要があります。

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.jsPage.getLayout プロパティを削除し、その後の ページの移行ステップに従い app ディレクトリに移動します。

    app/dashboard/page.js
    export default function Page() {
    return <p>My Page</p>
    }
  • pages ディレクトリの動作を保持するために DashboardLayout の内容を新しい Client Component に移動します。

    app/dashboard/DashboardLayout.js
    'use client' // このディレクティブはファイルの最上部、インポートの前に置く必要があります。

    // これは Client Component です
    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'

    // これは 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 コンポーネントを使用して titlemeta などの <head> HTML 要素を管理しますが、app ディレクトリでは next/head は新しい 組み込みの SEO サポート に置き換えられます。

前:

pages/index.tsx
import Head from 'next/head'

export default function Page() {
return (
<>
<Head>
<title>My page title</title>
</Head>
</>
)
}

後:

app/page.tsx
import type { Metadata } from 'next'

export const metadata: Metadata = {
title: 'My Page Title',
}

export default function Page() {
return '...'
}

すべてのメタデータオプションを表示

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

  • app ディレクトリ のページは、デフォルトで Server Components です。これは、pages ディレクトリのページが Client Components であることと異なります。
  • app ではデータフェッチが変更されました。getServerSidePropsgetStaticProps、および getInitialProps は、よりシンプルな 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: デフォルトエクスポートされたページコンポーネントを新しい 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 と同様に、初期ページ読み込み時の静的HTMLに Client Components を事前レンダリングするための最適化ステップがあります。
  • pages/index.js から app/home-page.tsx にデフォルトでエクスポートされたページコンポーネントを移動します。
app/home-page.tsx
'use client'

// これは Client Component です(`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 ファイルを作成します。これはデフォルトで Server Component です。
  • ページ内に home-page.tsx Client Component をインポートします。
  • pages/index.js でデータの取得を行っていた場合、新しい データフェッチ API を使用して、Server Component 内にデータフェッチロジックを直接移動します。詳細については、データフェッチ方法のアップグレードガイド を参照してください。
app/page.tsx
// 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 を訪問してください。アプリディレクトリ経由で提供される既存のインデックスルートが表示されます。

ステップ 5: ルーティングフックの移行

新しい動作をサポートするために新しいルーターが app ディレクトリに追加されました。

app では、next/navigation からインポートする三つの新しいフックを使用します: useRouter()usePathname()、および useSearchParams()

  • 新しい useRouter フックは next/navigation からインポートされ、next/router からインポートされる pages 内の useRouter フックとは異なる動作をします。
  • 新しい useRouter では pathname 文字列を返しません。代わりに usePathname フックを使用してください。
  • 新しい useRouter では query オブジェクトを返しません。検索パラメータと動的ルートパラメータが別々になりました。代わりに useSearchParamsuseParams フックを使用してください。
  • ページの変更を監視するには、useSearchParamsusePathname を一緒に使用できます。詳細については、Router Events セクションを参照してください。
  • これらの新しいフックは Client Components でのみサポートされています。Server Components では使用できません。
app/example-client-component.tsx
'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 の値は、組み込みの i18n Next.js 機能が app ディレクトリでは必要なくなったため削除されました。i18n について詳しく学ぶ
  • basePath が削除されました。代わりとなるものは useRouter の一部ではありません。まだ実装されていません。
  • asPath が削除されました。新しいルーターから as の概念が削除されました。
  • isReady は必要なくなったので削除されました。静的レンダリングの間に、useSearchParams() フックを使用するコンポーネントは事前レンダリングステップをスキップし、代わりにランタイムクライアントでレンダリングされます。
  • route が削除されました。usePathname または useSelectedLayoutSegments() により代替手段が提供されます。

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

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

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

ステップ 6: データフェッチ方法の移行

pages ディレクトリは、getServerSideProps および getStaticProps を使用してページ用のデータをフェッチします。app ディレクトリ内では、これらの以前のデータフェッチ関数は、fetch() および async React Server Components を基にしたよりシンプルな API に置き換えられます。

app/page.tsx
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 を使用して、サーバーでデータをフェッチし、その props をファイル内のデフォルトでエクスポートされた 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 では、Server Componentsを使用してReactコンポーネント内にデータフェッチを配置できます。これにより、クライアントに送信するJavaScriptの量を減らしながら、サーバーからレンダリングされたHTMLを保持できます。

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

app/dashboard/page.tsx
// `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/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 ディレクトリでは、リクエストデータを取得するための新しい読み取り専用の関数が公開されています:

  • headers: Web Headers API に基づいており、Server Components 内でリクエストヘッダーを取得するために使用できます。
  • cookies: Web Cookies API に基づいており、Server Components 内で cookie を取得するために使用できます。
app/page.tsx
// `app` ディレクトリ
import { cookies, headers } from 'next/headers'

async function getData() {
const authHeader = (await headers()).get('authorization')

return '...'
}

export default async function Page() {
// Server Components 内で`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を提供し、layouts内で使用できます。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 ディレクトリでの新しいモデルには generateStaticParams という名前が getStaticPaths より適切です。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 (デフォルト) に設定されている場合、まだ生成されていないルートセグメントがリクエストされたときには、それはサーバーレンダリングされキャッシュされます。

Incremental Static Regeneration (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 RequestResponse APIs を使用して、特定のルートに対するカスタムリクエストハンドラーを作成できます。

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

Good to know: クライアントから外部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 は、機能が廃止される場合にコードベースをアップグレードするのを助けるコード変換ツールを提供します。Codemodsで詳細を確認してください。