ウェブエンジニア問題集

レイアウトと共通UI — layout.tsxとネストレイアウトの仕組み

ほとんどのサイトには、全ページ共通のヘッダー・フッター・ナビゲーションがあります。これらをページごとに書いていてはメンテナンスが大変です。App Routerでは layout.tsx を使って、共通の「枠」を一度だけ定義できます。

この章では、ルートレイアウトとネストレイアウトの仕組み、そして「レイアウトは画面遷移しても再レンダリングされない」という重要な性質を押さえます。

学習者学習者

ヘッダーを全ページに出したいけど、各 page.tsx にコピペするのはさすがに違うよね…?

layout.tsx の役割 — ページを包む「枠」

layout.tsx は、同じフォルダおよびその配下のすべてのページを包み込む共通UIを定義します。受け取った children の位置に、各ページ(page.tsx)の中身が差し込まれます。

// src/app/layout.tsx
export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <html lang="ja">
      <body>
        <header>共通ヘッダー</header>
        <main>{children}</main> {/* ← ここに各ページが入る */}
        <footer>共通フッター</footer>
      </body>
    </html>
  );
}
先生先生

レイアウトは「額縁」、ページは「絵」。額縁はそのままで、中の絵(children)だけが差し替わる——このイメージが掴めれば完璧。

共通の枠の中にページが差し込まれるイメージ

ルートレイアウト — 必須の最上位レイアウト

app/layout.tsx(最上位のレイアウト)は ルートレイアウトと呼ばれ、App Routerでは必須です。アプリ全体の土台になるため、次の特徴があります。

  • <html><body> タグを必ず含める(他の場所には書かない)
  • 全ページで共通の lang 属性やグローバルCSSの読み込みをここで行う
  • サイト全体のデフォルトのメタデータもここで設定できる(詳細は第9章
// src/app/layout.tsx — ルートレイアウト
import "./globals.css";
import type { Metadata } from "next";
 
export const metadata: Metadata = {
  title: "My App",
  description: "Next.jsで作るアプリ",
};
 
export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="ja">
      <body>{children}</body>
    </html>
  );
}

ネストレイアウト — 一部のページにだけ枠を足す

レイアウトは、ルートだけでなく任意のフォルダに置けます。例えば「ダッシュボード配下のページにだけサイドバーを出したい」場合、app/dashboard/layout.tsx を作ります。

app/
├── layout.tsx              ← ルートレイアウト(全ページ共通)
└── dashboard/
    ├── layout.tsx          ← dashboard配下だけのレイアウト(サイドバー等)
    ├── page.tsx            → /dashboard
    └── settings/
        └── page.tsx        → /dashboard/settings
// src/app/dashboard/layout.tsx
export default function DashboardLayout({ children }: { children: React.ReactNode }) {
  return (
    <div className="flex">
      <aside className="w-64">サイドバー</aside>
      <div className="flex-1">{children}</div>
    </div>
  );
}

このとき、/dashboard/settings を開くと、レイアウトは外側から内側へ入れ子に適用されます。

flowchart TB
    A["ルートレイアウト(ヘッダー)"] --> B["dashboardレイアウト(サイドバー)"]
    B --> C["settings/page.tsx(中身)"]

レイアウトは「再レンダリングされない」

App Routerの重要な性質です。同じレイアウトを共有するページ間を移動しても、レイアウトは再マウントされず、状態が保持されます

例えばダッシュボードのサイドバーに開閉状態を持たせている場合、/dashboard から /dashboard/settings へ移動してもサイドバーの開閉状態は維持され、children(ページの中身)だけが切り替わります。

学習者学習者

ページを移動してもサイドバーのスクロール位置が戻らないの、地味だけど嬉しい…!

これはユーザー体験に直結する大事な挙動です。ナビゲーションやプレイヤーなど「移動しても状態を保ちたいUI」はレイアウトに置く、と覚えておきましょう。

template.tsx との違い

レイアウトに似たファイルに template.tsx があります。違いは1点だけです。

ファイル画面遷移したとき
layout.tsx再マウントされない(状態を保持)
template.tsx毎回再マウントされる(状態リセット・再アニメーション)

「ページ遷移のたびにフェードインさせたい」「遷移ごとに状態をリセットしたい」といった特殊なケースでは template.tsx を使いますが、基本は layout.tsx で十分です。

まとめ

  • layout.tsx は配下のページを包む共通の枠。children の位置にページが差し込まれる
  • app/layout.tsx(ルートレイアウト)は必須で、<html> <body> を含める
  • レイアウトはフォルダ単位で入れ子にでき、外側から内側へ適用される
  • 同じレイアウトを共有するページ間の移動ではレイアウトは再レンダリングされず状態を保持する
  • 遷移ごとにリセットしたい特殊なケースだけ template.tsx を使う

次の章では、ブログ記事や商品ページのように 動的ルーティング でURLの一部を変数として扱う方法を学びます。1つのファイルで何百ページも生成できるようになります。