App Router

アップ・ルーター

開発スピードが早いなぁという印象ですが、とうとう 2023 年 5 月、Next.js 13(v13.4.0) では新しい機能と規則を備えた新しい App Router の安定版が導入されました。App Router は app ディレクトリで使用できます。

App Router を分かりやすく

Next.js v13.4では、App Routerという新しい機能が安定版としてリリースされました。これは、Next.jsがどのように動作するかを決定する中心的な部分で、フレームワークの他の部分との統合も担当しています。

App Routerの新機能は、React Server Components、Nested Routes & Layouts、Simplified Data Fetching、Streaming & Suspense、Built-in SEO Supportなどを提供しています。これらの機能を詳しく見てみましょう。

React Server Components

サーバーサイドでReactコンポーネントをレンダリングすることができ、フロントエンドとバックエンドの間のデータフェッチングを最小限に抑えることができます。これにより、パフォーマンスが向上し、ユーザーエクスペリエンスが向上します。

Nested Routes & Layouts

より複雑なUIを構築することができます。例えば、特定のセクションで共通のレイアウトを使用することができます。これは、Webサイトのナビゲーションバーなど、複数のページに渡って一貫した要素を維持するのに便利です。

Simplified Data Fetching

データフェッチングをより直感的に行うことができます。以前は、データフェッチングのためにカスタム_appファイルを使用しなければならなかったのですが、新しいApp Routerでは、このような制限がなくなりました。

Streaming & Suspense

ページの一部がまだロードされていなくても、他の部分が対話可能であることを保証します。これは、ページの perceived loading performanceを向上させるのに役立ちます。

Built-in SEO Support

ページのSEOを向上させるためのツールを提供します。これにより、検索エンジンがページを適切にインデックス化し、ランキングを向上させることができます。

Next.js 13.4 App Router の特徴

雑に特徴をまとめました。

  • app ディレクトリは pages ディレクトリと同じように動作する
  • next.config.js : appDir オプションは、Next.js 13.4 以降は必要ない
  • すべて関数
  • App Router は Pages Router よりも優先される
  • 内部のコンポーネントは、デフォルトで Server Components になっている
  • Client Components に変更することができる
  • ディレクトリはルートを定義するために使用される
  • ルート内の各ディレクトリは、ルートセグメントを表す
  • page.tsx は、index.tsx と同じ
  • page.tsx, layout.tsx を配置していく
  • Turbopack (ベータ) コンパイルが早い
  • 組み込み SEO サポートを使用して、ページごとにメタデータを設定できる
  • Server Components により async/await が使える
  • 理解しやすい構成になった
  • レイアウトとページの間でデータを受け渡せない
  • Client Component から Server Component を利用することが出来ない
  • useRouter は、 next/router ではなく next/navigation からインポート

App Router は、共有レイアウト、ネストされたルーティング、状態の読み込み、エラー処理などをサポートします。

Next.js 13.4.0では、App Routerという機能が導入され、次の追加機能が含まれています。これらの機能は、不必要な破壊的変更を避けながら逐次的に採用することが可能です。

Layouts

ルート間でUIを簡単に共有し、状態を保持しつつ、高コストな再レンダリングを避けることができます。

Server Components

最も動的なアプリケーションでサーバーファーストをデフォルトにすることができます。

Streaming

インスタンスのロード状態を表示し、UIの単位がレンダリングされると同時にストリームできます。

Simplified Data Fetching

async Server Componentsと拡張されたfetch APIにより、コンポーネントレベルでのフェッチングが可能になります。

Built-in SEO Support

新しいMetadata APIにより、静的および動的なmetaタグを設定することができます。

App Router に変更した場合の、エラーが出るであろう場所

  • useRouter(現在 router.events はサポート外)
  • getStaticPath / getStaticProps
  • Google Analytics
  • asPath は使えない
  • router.events は使えない

※ router.events は Next.js 13 にはもう存在しないのでエラーが出ます。

色々と分かりやすくなった印象ですが、上述した場所でエラーが頻出するので、段階的に移行する必要があります。これらは一部の可能性のあるエラー発生場所ですが、具体的なエラーはプロジェクトの構造や使用されている技術により異なります。App Router を導入する前には、十分なテストを行うことが重要です。

Data Fetching

これまでの getStaticProps や getServerSideProps は App Router では使用することができません。代替手段として Server Component で async/await を使用してデータを取得します。そして、Fetch API を利用してデータの取得時にキャッシュやリクエストの重複排除を行います。

Server Components(SC)

App Router 内のコンポーネントはデフォルトで Server Component になります。useState, useEffect, Context などのフックやイベントハンドラーは使用できません。どうしても使いたい場合はファイルの先頭で "use client" を宣言して、Client Component(CC) に変更する必要があります。

基本的に SC は、データを取得したりバックエンド リソースに (直接) アクセスする場合に使います。

// app/post/[id]/page.tsx
export async function generateStaticParams() {
  return [{ id: "1" }, { id: "2" }]
}

async function fetchPost (params) {
  const response = await fetch('https://.../${params.id}`)
  const data = await response.json()

  return data.post
}

export default async function Post({ params }) {
  const post = await fetchPost (params)

  return <Post post={post} />
}

Client Component(CC)

CC は、クライアント側の対話機能をアプリケーションに追加できます。'use client'ディレクティブを使用して、クライアントコンポーネントをオプトインすることもできます。コンポーネントの先頭に'use client' を記述し export して使うのが基本です。

Next.js では、それらはサーバーで事前にレンダリングされ、クライアントでハイドレートされます。また、Client Component から Server Component を利用することは出来ません。

CC は、useState、useEffect、useReducer など、ブラウザーのみの API に依存するカスタムフックを使用する場合に使います。

'use client'
import { useRouter } from 'next/navigation'

13.4へ移行するには?

Next.jsの新しいappディレクトリとApp Routerを使用して、既存のpagesディレクトリから移行する方法について説明します。この移行は一部のReact機能、特にServer ComponentsやSuspenseなどを初めて使用する場合がありますが、新しいNext.jsの機能と組み合わせることで新たなコンセプトや行動パターンを学習する必要があります。移行を小さなステップに分けて行うことが推奨されています。

移行の手順は以下のとおりです。

  1. 最新のNext.jsバージョン(13.4以上が必要)にアップデートし、プロジェクトのルート(またはsrc/ディレクトリ)に新しいappディレクトリを作成します。
  2. appディレクトリ内に新しいapp/layout.tsxファイルを作成します。これはapp内のすべてのルートに適用されるルートレイアウトです。ルートレイアウトは<html><body>タグを必ず定義する必要があります。また、pages/_app.tsxpages/_document.tsxファイルを置き換えます。
  3. 既存の_app_documentファイルがある場合は、その内容(例えば、グローバルスタイル)をルートレイアウト(app/layout.tsx)にコピーします。移行が完全に終わったら、これらのファイルは安全に削除できます。
  4. pagesディレクトリのページごとのレイアウトを実現するために、Page.getLayoutというパターンが推奨されていましたが、これはappディレクトリのネストされたレイアウトのネイティブサポートで置き換えられます。
  5. pagesディレクトリではnext/headコンポーネントが<head>HTML要素を管理していましたが、appディレクトリではこれが新しい組み込みのSEOサポートに置き換えられます。
  6. 最後に、pagesディレクトリのページをappディレクトリに移行します。このとき、データフェッチングの方法が変わり、getServerSidePropsgetStaticPropsgetInitialPropsがよりシンプルなAPIに置き換えられます。

また、appディレクトリではネストされたフォルダを使用してルートを定義し、特殊なpage.jsファイルを使用してルートセグメントを公開します。ページの移行は、まずデフォルトでエクスポートされたPageコンポーネントを新しいClientコンポーネントに移動し、次にその新しいClientコンポーネントをappディレクトリ内の新しいpage.jsファイルにインポートする、という2つの主要なステップに分けて行うことが推奨されています。

App Router

pages.tsx

pages.tsx はこれだけで動作します。

export default function Home() {
  return <h1>HOME です。</h1>
}

layout.tsx

layout.tsx はこのようになっています。metadata は、組み込み SEO であり、ページごとに設定できます。

import './globals.css'
import { Inter } from 'next/font/google'

const inter = Inter({ subsets: ['latin'] })

export const metadata = {
  title: 'サンプルです',
  description: 'Generated by create next app',
}

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="en">
      <body className={inter.className}>{children}</body>
    </html>
  )
}

page.js ルートは、固有の UI を定義するために使用します。 layout.js は、複数のルートで共有される UI を定義するために使用します。

next/head

pages/index.tsx に次のように記述していましたが、

import Head from 'next/head'

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

app/page.tsx は、次のように記述します。

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

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

ページ規則

Next.js は、ネストされたルートで特定の動作をする UI を作成するための特別なファイルのセットを提供します。

page.js

ルートの独自の UI を作成し、パスをパブリックにアクセスできるようにします。

route.js

ルートのサーバー側 API エンドポイントを作成します。

layout.js

セグメントとその子の共有 UI を作成します。レイアウトは、ページまたは子セグメントをラップします。

template.js

layout.js に似ていますが、新しいコンポーネント インスタンスがナビゲーションにマウントされます。

loading.js

セグメントとその子のロード UI を作成します。読み込み中の読み込み UI を示します。

error.js

セグメントとその子のエラー UI を作成します。エラーがキャッチされた場合にエラー UI を表示します。

移行フロー

pages/_app.js や、pages/_document.jsapp/layout.js に置き換えます。 コンテンツ (グローバル スタイルなど) をルート レイアウト ( app/layout.tsx) にコピーできます。

pages/_error.js は、error.js に置き換えられました。 pages/404.jsは、not-found.js に置き換えられました。

pages から置き換えるときは、次のようにします。

  • ルートディレクトリを app に変更
  • index.js は page.js に変更
  • _app.tsxlayout.tsx
  • _document.tsxlayout.tsx に統合する
  • Header は head.tsx
  • URL の重複を避けるため pages 以下は削除する
  • useRouter を next/router から next/navigation に書き換える。'use client' をファイルの先頭に追加

インストール

# インストール
npx create-next-app@latest v13.4

インストール時に、次のプロンプトが表示されます。App Router を使う場合は、Use App Router のところで Yes を選択します。pages を使う場合は、Use App Router のところで No を選択します。

✔ Would you like to use TypeScript with this project? … Yes
✔ Would you like to use ESLint with this project? … Yes
✔ Would you like to use Tailwind CSS with this project? … Yes
✔ Would you like to use `src/` directory with this project? … Yes
✔ Use App Router (recommended)? … Yes
✔ Would you like to customize the default import alias? … No

作成後のディレクトリはこのようになっています。これは App Router と呼ばれる機能で、従来の pages ディレクトリとは異なるレイアウトシステムを採用しています。

.
├── public
└── src/
    └── app/
        ├── layout.tsx
        └── page.tsx

getStaticProps

getServerSideProps 及び getStaticProps は使えないので、他の方法で代替する必要があります。

export async function generateStaticParams() {
  const posts = await fetch('https://.../posts').then((res) => res.json())

  return posts.map((post) => ({
    slug: post.slug,
  }))
}

export default function Page({ params }: { params: { slug: string } }) {
  const { slug } = params
  // ...
}

Pages Router はなくなりますか?

という質問に公式ブログは次のように答えています。

いいえ。バグ修正、改善、セキュリティ パッチなど、今後の複数のメジャー バージョンの開発をサポートすることをお約束します。私たちは、開発者が App Router を段階的に導入するための十分な時間を確保したいと考えています。

Blog - Next.js 13.4 | Next.js