Skip to content

5.3 Next.js App Router 入門 ​

5.1 と 5.2 で見た RSC を、実務で実際に動かすためのフレームワークが Next.js です。Next.js には 2 種類のルーターがあり、 RSC を前提にしているのは App Router(app/ ディレクトリ)のほうです。もう 1 つの Pages Router(pages/ ディレクトリ)は従来の SPA スタイルで、 RSC は使えません。案件で新規に Next.js を触ると App Router 側になることが多いので、 この章では App Router に絞って構造を見ていきます。

公式チュートリアルの代わりではなく、 案件でコードを読んだときに「あ、これか」と分かるための地図 を作るのが、この章の目的です。詳細は nextjs.org/docs を参照してください。

5.3.1 ディレクトリがそのままルーティングになる ​

App Router は ファイルシステムベースのルーティング です。app/ 以下のフォルダ名が URL のパスになり、フォルダの中の決まったファイル名(page.tsx など)が画面を決めます。

図 5.3.1 app/ とルートの対応 ​

「フォルダ = ルートセグメント、ファイル = そのセグメントの UI」 という対応になっています。公式の言い方は次の通りです。

Folders are used to define the route segments that map to URL segments. Files (like page and layout) are used to create UI that is shown for a segment. (nextjs.org / Layouts and Pages)

図 5.3.2 app/ ディレクトリツリーと特殊ファイルの役割 ​

ファイル名のうち、特別な意味を持つもの( page.tsx / layout.tsx / loading.tsx / error.tsx / not-found.tsx / route.ts )は、 置いた場所で役割が決まります。次の図で全体像をつかんでみてください。

ディレクトリツリーapp/├─ layout.tsx├─ page.tsx├─ loading.tsx├─ error.tsx├─ not-found.tsx├─ about/│ └─ page.tsx├─ blog/│ ├─ layout.tsx│ ├─ page.tsx│ └─ [slug]/│ └─ page.tsx└─ api/ └─ route.ts役割 root layout— html / body とアプリ全体の枠
<!-- page.tsx (top) --> <rect x="360" y="80" width="380" height="32" rx="4" fill="#888" fill-opacity="0.08" stroke="currentColor" opacity="0.45" /> <text x="372" y="100" font-weight="bold">トップページ</text> <text x="500" y="100" opacity="0.8">— URL "/" の画面本体</text> <!-- loading.tsx --> <rect x="360" y="118" width="380" height="32" rx="4" fill="#888" fill-opacity="0.08" stroke="currentColor" opacity="0.45" /> <text x="372" y="138" font-weight="bold">loading UI</text> <text x="500" y="138" opacity="0.8">— Suspense のフォールバック</text> <!-- error.tsx --> <rect x="360" y="156" width="380" height="32" rx="4" fill="#888" fill-opacity="0.08" stroke="currentColor" opacity="0.45" /> <text x="372" y="176" font-weight="bold">error UI</text> <text x="500" y="176" opacity="0.8">— このセグメントの Error Boundary</text> <!-- not-found.tsx --> <rect x="360" y="194" width="380" height="32" rx="4" fill="#888" fill-opacity="0.08" stroke="currentColor" opacity="0.45" /> <text x="372" y="214" font-weight="bold">404 ページ</text> <text x="500" y="214" opacity="0.8">— notFound() と組み合わせて使う</text> <!-- about/page.tsx --> <rect x="360" y="232" width="380" height="32" rx="4" fill="#888" fill-opacity="0.08" stroke="currentColor" opacity="0.45" /> <text x="372" y="252" font-weight="bold">/about</text> <text x="500" y="252" opacity="0.8">— about/ フォルダ直下の画面</text> <!-- blog/layout.tsx --> <rect x="360" y="270" width="380" height="32" rx="4" fill="#888" fill-opacity="0.08" stroke="currentColor" opacity="0.45" /> <text x="372" y="290" font-weight="bold">blog サブ layout</text> <text x="500" y="290" opacity="0.8">— /blog 以下で共有される枠</text> <!-- blog/page.tsx --> <rect x="360" y="308" width="380" height="32" rx="4" fill="#888" fill-opacity="0.08" stroke="currentColor" opacity="0.45" /> <text x="372" y="328" font-weight="bold">/blog 一覧</text> <text x="500" y="328" opacity="0.8">— 記事一覧ページ</text> <!-- [slug]/page.tsx --> <rect x="360" y="346" width="380" height="32" rx="4" fill="#3498db" fill-opacity="0.1" stroke="currentColor" opacity="0.6" /> <text x="372" y="366" font-weight="bold">/blog/[slug]</text> <text x="500" y="366" opacity="0.8">— 動的セグメント(記事詳細)</text> 

以降の節で、 これらのファイルの中身を順に見ていきましょう。

5.3.2 覚えておく特殊ファイル ​

App Router にはフォルダの中で特別な意味を持つファイル名がいくつかあります。最低限これだけ知っておけば、 案件のコードはだいたい読めます。

ファイル名役割
page.tsxそのセグメントの画面本体。default export が必要
layout.tsxそのセグメント以下で共有される枠組み。children を受け取る
loading.tsxそのセグメントがロード中に出す UI(内部的に Suspense のフォールバック)
error.tsxそのセグメント以下でエラーが起きたときの UI(Error Boundary)
not-found.tsx404 ページ
route.tsそのセグメントを HTTP ハンドラにする(GET / POST 等)

page.tsx は必須で、 これを置いて初めてその URL が公開されます。

A page file is required to make a route segment publicly accessible. (nextjs.org / page.js)

page.tsx の最小形 ​

tsx
// app/page.tsx
export default function Page() {
  return <h1>Hello Next.js!</h1>;
}

layout.tsx の最小形(root layout) ​

tsx
// app/layout.tsx
export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <html lang="ja">
      <body>
        <header>ヘッダー</header>
        <main>{children}</main>
      </body>
    </html>
  );
}

app/layout.tsx は root layout と呼ばれます。これは 必須 で、 <html> と <body> を書くのはここだけ というルールになっています。

The root layout is required and must contain html and body tags. (nextjs.org / Layouts and Pages)

サブディレクトリに layout.tsx を置くと、 そのセグメント以下で共有される枠になります。入れ子にもできます。

図 5.3.3 layout / page のネスト構造 ​

/blog/hello を開くと、 root layout > blog layout > [slug] page が外側から順に入れ子になってレンダーされます。ナビゲーション時、 layout は 再レンダーされずに残る のが特徴です。

On navigation, layouts preserve state, remain interactive, and do not rerender. (nextjs.org / Layouts and Pages)

5.3.3 動的セグメント ​

URL の一部が可変なページ(ブログの記事や商品の詳細など)は、 フォルダ名を [ ] で囲みます。

書き方URL 例params
app/blog/[slug]/page.tsx/blog/hello{ slug: 'hello' }
app/shop/[category]/[item]/page.tsx/shop/shoes/42{ category: 'shoes', item: '42' }
app/blog/[...slug]/page.tsx/blog/a/b/c{ slug: ['a', 'b', 'c'] }

page.tsx の props として params を受け取ります。Next.js 15 以降、 params は Promise なので await して取り出す形になっています。

tsx
// app/blog/[slug]/page.tsx
export default async function Page({
  params,
}: {
  params: Promise<{ slug: string }>;
}) {
  const { slug } = await params;
  const post = await getPost(slug);
  return (
    <article>
      <h1>{post.title}</h1>
      <p>{post.body}</p>
    </article>
  );
}

Since the params prop is a promise, you must use async/await or React's use function to access the values. (nextjs.org / page.js)

クエリ文字列(?page=2 のようなもの)は searchParams で受け取れます。こちらも Promise です。

5.3.4 ルートグループとスロット ​

もう 2 つ、 案件コードでよく見る書き方に軽く触れておきます。

  • Route Groups(ルートグループ): フォルダ名を ( ) で囲むと、 そのフォルダ名は URL に含まれなくなります。app/(marketing)/about/page.tsx は /about として公開されます。同じ URL 構造のまま、 コード上は (marketing) / (shop) のようにグループ分けする用途で使います。
  • Parallel Routes(パラレルルート / 名前付きスロット): フォルダ名を @ で始めると、 layout の props として名前付きスロットになります。app/@analytics/page.tsx は layout の props に analytics として渡されます。ダッシュボードのように 1 画面に複数の独立した領域を並べるときに使われます。

いずれも Next.js Docs の File-system conventions に正式な項目として載っています(Route Groups / Parallel Routes)。この章では概要のみ触れて、 詳細は公式ドキュメントに委ねます。

画面間の移動には、 next/link の Link コンポーネントを使います。素の <a> ではなくこちらを使う理由は、 プリフェッチ と クライアントサイドトランジション が効くからです。

tsx
// app/ui/nav.tsx
import Link from 'next/link';

export default function Nav() {
  return (
    <nav>
      <Link href="/">トップ</Link>
      <Link href="/blog">ブログ</Link>
    </nav>
  );
}

公式の説明を引いておきます。

Next.js automatically prefetches routes linked with the <Link> component when they enter the user's viewport. (nextjs.org / Linking and Navigating)

<Link> がビューポートに入ると、 その遷移先のルートをバックグラウンドで取りに行きます。ユーザーがクリックした時点ではもうデータが手元にあることが多く、 遷移は即時に感じられます。

素の <a> を使うと、 フルページ遷移(リロード)になってしまうので、 この最適化は効かなくなります。

5.3.6 データ取得は Server Component の中で直接 ​

SPA では useEffect の中で fetch を呼ぶのが定型パターンでした。App Router では、 Server Component の関数を async にして await するだけ で済みます。

tsx
// app/blog/page.tsx
export default async function Page() {
  const res = await fetch('https://api.example.com/posts');
  const posts = await res.json();
  return (
    <ul>
      {posts.map((p: { id: string; title: string }) => (
        <li key={p.id}>{p.title}</li>
      ))}
    </ul>
  );
}

To fetch data with the fetch API, turn your component into an asynchronous function, and await the fetch call. (nextjs.org / Fetching Data)

データベースクライアントや ORM を直接呼ぶこともできます。認証トークンなどの秘密情報も、 Server Component の中なら安全に扱えます。クライアントバンドルには含まれないからです。

キャッシュ挙動に注意(Next.js 15+) ​

Next.js 15 以降、 Server Component 内の fetch は デフォルトではキャッシュされず、 リクエストごとに新規取得 になります。公式ドキュメントの原文は次のとおりです。

fetch requests are not cached by default and will block the page from rendering until the request is complete. (nextjs.org / Fetching Data)

毎リクエストごとに外部 API を叩きたくない場合は、 次のいずれかでキャッシュに乗せます。

  • fetch(url, { cache: 'force-cache' }) — 明示的にキャッシュする
  • fetch(url, { next: { revalidate: 60 } }) — 指定秒ごとに再検証しつつキャッシュする
  • Next.js 16 以降の use cache ディレクティブ — コンポーネントや関数単位でキャッシュする

詳細は Next.js Docs — Caching と fetch API リファレンス を参照してください。

遅いデータがあるときは、 loading.tsx を置くか <Suspense> で囲むことで、 画面のほかの部分を先に見せることができます(4.3 参照)。

5.3.7 <head> は metadata で ​

タイトル、OGP、description といった <head> 要素は、 手で <title> を書くのではなく metadata export を使います。

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

export const metadata: Metadata = {
  title: 'My Next.js Site',
  description: 'React 学習サンプル',
};

export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <html lang="ja">
      <body>{children}</body>
    </html>
  );
}

各 page.tsx や layout.tsx で個別の metadata を export すると、 外側のものを上書きできます。

You should not manually add <head> tags such as <title> and <meta> to root layouts. Instead, you should use the Metadata API. (nextjs.org / layout.js)

5.3.8 案件で見るときのポイント ​

最後に、 案件の Next.js コードを読み始めるとき、 最初に見るとよい場所を並べておきます。

  • app/layout.tsx — root layout。<html> / <body> とアプリ全体のスタイルやプロバイダがここにまとまっています
  • app/page.tsx — トップページ
  • app/**/page.tsx — ページの実体。各ファイルの冒頭を見れば、 Server Component(ディレクティブなし、async で OK)か Client Component('use client')かが分かります
  • next.config.js / next.config.ts — ビルド設定。リライト・リダイレクト・画像ドメインなど
  • middleware.ts(または proxy.ts 系) — リクエスト前処理
  • app/**/route.ts — HTTP ハンドラ(API エンドポイント相当)

'use client' の有無を冒頭で確認する癖を付けておくと、 「このファイルはサーバー側で動く」「ここからブラウザ側」 という切り分けがすぐに掴めるようになります。

次の章では、 フォーム送信とデータ更新 を Server Actions でどう書くかを見ていきましょう。

参照ソース ​