ページからアプリへ
このガイドでは、次のことを行います。
- Next.jsアプリケーションをバージョン12からバージョン13にアップデートする
pages
ディレクトリとapp
ディレクトリの両方で動作する機能をアップグレードする- 既存のアプリケーションを
pages
からapp
に段階的に移行する
アップグレード
Node.jsバージョンを参照してください。
Next.jsバージョン
Next.jsバージョン13にアップデートするには、お好みのパッケージマネージャーを使用して次のコマンドを実行します。
npm install next@latest react@latest react-dom@latest
ESLintバージョン ターミナルnpm install -D eslint-config-next@latest
npm install -D eslint-config-next@latest
知っておくと便利: ESLintの変更を有効にするには、VS CodeでESLintサーバーを再起動する必要がある場合があります。コマンドパレット (Macでは
cmd+shift+p
、Windowsではctrl+shift+p
) を開き、ESLint: ESLintサーバーの再起動
を検索します。
次のステップ
アップデート後、次のステップについては、以下のセクションを参照してください。
- 新機能のアップグレード: 改善された画像コンポーネントやリンクコンポーネントなど、新機能へのアップグレードに役立つガイド。
pages
ディレクトリからapp
ディレクトリへの移行:pages
ディレクトリからapp
ディレクトリに段階的に移行するためのステップバイステップガイド。
新機能へのアップグレード
Next.js 13では、新しい機能と規約を備えた新しいApp Routerが導入されました。新しいRouterはapp
ディレクトリで使用でき、pages
ディレクトリと共存します。
Next.js 13へのアップグレードでは、新しいApp Routerを使用する必要はありません。 更新されたImageコンポーネント、Linkコンポーネント、Scriptコンポーネント、フォントの最適化など、両方のディレクトリで動作する新しい機能とともに、pages
を引き続き使用できます。
<Image/>
コンポーネント next-image-to-legacy-image
コードモッド :next/image
インポートをnext/legacy/image
に安全かつ自動的に名前変更します。既存のコンポーネントは同じ動作を維持します。
next-image-to-legacy-image
コードモッドnext-image-experimental
コードモッド:インラインスタイルを危険に追加し、未使用のプロップを削除します。これは、既存のコンポーネントの動作を新しいデフォルトと一致するように変更します。このコードモッドを使用するには、最初にnext-image-to-legacy-image
コードモッドを実行する必要があります。<Link>
コンポーネント
<Link>
コンポーネントでは、子として<a>
タグを手動で追加する必要がなくなりました。この動作はバージョン12.2<Link>
は常に<a>
をレンダリングし、基になるタグにプロップを転送できます。
例えば
import Link from 'next/link'
// Next.js 12: `<a>` has to be nested otherwise it's excluded
<Link href="/about">
<a>About</a>
</Link>
// Next.js 13: `<Link>` always renders `<a>` under the hood
<Link href="/about">
About
</Link>
リンクをNext.js 13にアップグレードするには、new-link
コードモッドを使用できます。
<Script>
コンポーネント
next/script
の動作は、pages
とapp
の両方をサポートするように更新されましたが、スムーズな移行を確実にするためにいくつかの変更を行う必要があります
- 以前に
_document.js
に含めていたbeforeInteractive
スクリプトをルートレイアウトファイル(app/layout.tsx
)に移動します。 - 実験的な
worker
戦略はまだapp
では機能せず、この戦略で示されたスクリプトは削除するか、別の戦略(例:lazyOnload
)を使用するように変更する必要があります。 onLoad
、onReady
、およびonError
ハンドラーはサーバーコンポーネントでは機能しないため、クライアントコンポーネントに移動するか、完全に削除してください。
フォントの最適化
以前は、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の機能(サーバーコンポーネント、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
ルートレイアウトに置き換えられました。 詳細はこちら。pages/_error.js
は、より詳細なerror.js
特別なファイルに置き換えられました。 詳細はこちら。pages/404.js
は、not-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({
// Layouts must accept a children prop.
// This will be populated with nested layouts or pages
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サポートを使用できます
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 Context プロバイダーを使用している場合は、クライアントコンポーネントに移動する必要があります。
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
の内容を新しい クライアントコンポーネント に移動して、pages
ディレクトリの動作を維持します。app/dashboard/DashboardLayout.js'use client' // this directive should be at top of the file, before any imports. // This is a 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' // This is a Server Component export default function Layout({ children }) { return <DashboardLayout>{children}</DashboardLayout> }
-
DashboardLayout.js
(クライアントコンポーネント) の非インタラクティブな部分をlayout.js
(サーバーコンポーネント) に段階的に移動することで、クライアントに送信するコンポーネントの JavaScript の量を削減できます。
ステップ3: next/head
の移行
pages
ディレクトリでは、next/head
React コンポーネントを使用して、title
や meta
などの <head>
HTML 要素を管理します。 app
ディレクトリでは、next/head
は新しい 組み込み SEO サポート に置き換えられます。
移行前
import Head from 'next/head'
export default function Page() {
return (
<>
<Head>
<title>My page title</title>
</Head>
</>
)
}
移行後
import type { Metadata } from 'next'
export const metadata: Metadata = {
title: 'My Page Title',
}
export default function Page() {
return '...'
}
ステップ4: ページの移行
app
ディレクトリ のページは、デフォルトで サーバーコンポーネント です。これは、ページが クライアントコンポーネント であるpages
ディレクトリとは異なります。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: デフォルトでエクスポートされたページコンポーネントを新しいクライアントコンポーネントに移動します。
- ステップ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'
// This is a Client Component (same as components in the `pages` directory)
// It receives data as props, has access to state and effects, and is
// prerendered on the server during the initial page load.
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// Import your 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() { // Fetch data directly in a Server Component const recentPosts = await getPosts() // Forward fetched data to your Client Component return <HomePage recentPosts={recentPosts} /> }
-
以前のページで
useRouter
を使用していた場合は、新しいルーティングフックに更新する必要があります。 詳細はこちら。 -
開発サーバーを起動し、
https://#:3000
にアクセスします。既存のインデックスルートが表示され、現在は app ディレクトリを介して提供されています。
ステップ5: ルーティングフックの移行
app
ディレクトリの新しい動作をサポートするために、新しいルーターが追加されました。
app
では、next/navigation
からインポートされた3つの新しいフックを使用する必要があります:useRouter()
、usePathname()
、useSearchParams()
。
- 新しい
useRouter
フックはnext/navigation
からインポートされ、next/router
からインポートされるpages
のuseRouter
フックとは異なる動作をします。next/router
からインポートされたuseRouter
フック はapp
ディレクトリではサポートされていませんが、pages
ディレクトリでは引き続き使用できます。
- 新しい
useRouter
はpathname
文字列を返しません。代わりに、別のusePathname
フックを使用してください。 - 新しい
useRouter
はquery
オブジェクトを返しません。検索パラメータと動的ルートパラメータは分離されました。代わりに、useSearchParams
フックとuseParams
フックを使用してください。 useSearchParams
とusePathname
を一緒に使用して、ページの変更をリッスンできます。詳細は、ルーターイベント セクションを参照してください。- これらの新しいフックは、クライアントコンポーネントでのみサポートされています。サーバーコンポーネントでは使用できません。
'use client'
import { useRouter, usePathname, useSearchParams } from 'next/navigation'
export default function ExampleClientComponent() {
const router = useRouter()
const pathname = usePathname()
const searchParams = useSearchParams()
// ...
}
さらに、新しい useRouter
フックには、以下の変更点があります
fallback
が 置き換えられたため、isFallback
は削除されました。- 組み込みの i18n Next.js 機能は
app
ディレクトリでは不要になったため、locale
、locales
、defaultLocales
、domainLocales
の値は削除されました。 i18n の詳細はこちら。 basePath
は削除されました。代替案はuseRouter
の一部にはなりません。まだ実装されていません。- 新しいルーターから
as
の概念が削除されたため、asPath
は削除されました。 - 不要になったため、
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 サーバーコンポーネント上に構築された よりシンプルな API に置き換えられます。
export default async function Page() {
// This request should be cached until manually invalidated.
// Similar to `getStaticProps`.
// `force-cache` is the default and can be omitted.
const staticData = await fetch(`https://...`, { cache: 'force-cache' })
// This request should be refetched on every request.
// Similar to `getServerSideProps`.
const dynamicData = await fetch(`https://...`, { cache: 'no-store' })
// This request should be cached with a lifetime of 10 seconds.
// Similar to `getStaticProps` with the `revalidate` option.
const revalidatedData = await fetch(`https://...`, {
next: { revalidate: 10 },
})
return <div>...</div>
}
サーバーサイドレンダリング (getServerSideProps
)
pages
ディレクトリでは、getServerSideProps
はサーバー上でデータを取得し、ファイル内のデフォルトでエクスポートされた React コンポーネントに props を転送するために使用されます。ページの初期 HTML はサーバーからプリレンダリングされ、ブラウザでページが「ハイドレート」されます(インタラクティブになります)。
// `pages` directory
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` directory
// This function can be named anything
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` directory
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 に基づいており、サーバーコンポーネント 内で使用してリクエストヘッダーを取得できます。cookies
: Web Cookies API に基づいており、サーバーコンポーネント 内で使用して Cookie を取得できます。
// `app` directory
import { cookies, headers } from 'next/headers'
async function getData() {
const authHeader = (await headers()).get('authorization')
return '...'
}
export default async function Page() {
// You can use `cookies` or `headers` inside Server Components
// directly or in your data fetching function
const theme = (await cookies()).get('theme')
const data = await getData()
return '...'
}
静的サイト生成 (getStaticProps
)
pages
ディレクトリでは、`getStaticProps` 関数を使用して、ビルド時にページをプリレンダリングします。この関数は、外部 API やデータベースから直接データを取得し、ビルド中に生成される際にこのデータをページ全体に渡すために使用できます。
// `pages` directory
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` directory
// This function can be named anything
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` directory
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` directory
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` 接頭辞は、より記述的な `generate` に置き換えられました。これは、`getStaticProps` と `getServerSideProps` が不要になったため、単独でより適切に配置されます。 `Paths` 接尾辞は `Params` に置き換えられました。これは、複数の動的セグメントを持つネストされたルーティングに適しています。
`fallback` の置き換え
`pages` ディレクトリでは、`getStaticPaths` から返される `fallback` プロパティを使用して、ビルド時にプリレンダリングされないページの動作を定義します。このプロパティは、ページの生成中にフォールバックページを表示するには `true` に、404 ページを表示するには `false` に、リクエスト時にページを生成するには `blocking` に設定できます。
// `pages` directory
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'` オプションを置き換えます。ストリーミングを使用すると `'blocking'` と `true` の違いは無視できるため、`fallback: 'blocking'` オプションは `dynamicParams` に含まれていません。
// `app` directory
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` (デフォルト) に設定すると、生成されていないルートセグメントがリクエストされた場合、サーバーレンダリングされてキャッシュされます。
増分静的再生成 ( `revalidate` を使用した `getStaticProps` ) pages/index.js// `pages` directory
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>
)
}
// `pages` directory
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>
)
}