デザインについての学習メモブログ

Next.js 入門 #3: レイアウトとコンポーネント設計 — 共通ヘッダー・フッターを作ろう

記事内に広告が含まれています。

Next.js 入門 #3: レイアウトとコンポーネント設計 — 共通ヘッダー・フッターを作ろう

前回の記事でページとルーティングを覚えました。

ただ、今の状態ではページごとにバラバラなHTMLが表示されるだけです。

実際のWebサイトには、すべてのページに共通のヘッダーやフッターがありますよね。

今回はその「共通部分」を一か所で管理する仕組み、レイアウトを学びます。

あわせて、Server ComponentとClient Componentの使い分けという、Next.jsを理解する上でもっとも重要な概念にも踏み込みます。

layout.tsx の役割

app/ フォルダに最初から存在する layout.tsx を開いてみましょう。

TSX
// app/layout.tsx(初期状態)

import type { Metadata } from 'next';

export const metadata: Metadata = {
  title: 'My App',
  description: '...',
};

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

//RootLayout(引数)の部分が読みにくいが以下のように考える
//RootLayout({ children }: { children: React.ReactNode })
//           └─ ① ───┘   └────────── ② ──────────┘
//①は分割代入
//②は型の指定


//型定義してみやすくしても良い
// 1. まず、引数の「説明書(型)」を別に定義する
// type Props = {
//  children: React.ReactNode;
// };

// 2. 関数側では、その型をシンプルに指定する            ↓
//  export default function RootLayout({ children }: Props) {
//      //省略
//  }

{children} の部分に、各ページの内容(page.tsx)が流し込まれます。

つまり構造はこうなっています。

Bash
layout.tsx
  └── {children}  ここに page.tsx の内容が入る
        ├── app/page.tsx(/)
        ├── app/about/page.tsx(/about)
        └── app/blog/page.tsx(/blog)

layout.tsx に書いたものはすべてのページに共通して表示されます

ヘッダーやフッターを置く場所はここです。

💡 metadata とは?

一言でいうと、「Googleの検索エンジンや、SNS(XやLINEなど)にページの情報を伝えるための設定」です。

ここに書いた内容は、画面の本文(<body> の中)には表示されません。

ブラウザのタブに表示される文字(title)や、HTMLの裏側に隠れている <head> タグの中に自動的に変換されて埋め込まれます。

ヘッダーとフッターを作る

まず、ヘッダーとフッターをコンポーネントとして別ファイルに切り出します。

コンポーネントを置く場所は app/ ではなく、プロジェクトルートに components/ フォルダを作るのが一般的です。

Bash
my-first-nextjs/
├── app/
└── components/       ここに共通パーツを置く
    ├── Header.tsx
    └── Footer.tsx

Header.tsx

TSX
// components/Header.tsx

import Link from 'next/link';

export default function Header() {
  return (
    <header style={{ borderBottom: '1px solid #eee', padding: '1rem 2rem' }}>
      <nav style={{ display: 'flex', gap: '1.5rem', alignItems: 'center' }}>
        <Link href="/" style={{ fontWeight: 'bold', fontSize: '1.2rem' }}>
          My Blog
        </Link>
        <Link href="/blog">ブログ</Link>
        <Link href="/about">About</Link>
      </nav>
    </header>
  );
}

Footer.tsx

TSX
// components/Footer.tsx

export default function Footer() {
  return (
    <footer style={{ borderTop: '1px solid #eee', padding: '1rem 2rem', marginTop: '2rem' }}>
      <p style={{ color: '#888', fontSize: '0.875rem' }}>
        © 2026 My Blog. Built with Next.js.
      </p>
    </footer>
  );
}

layout.tsx に組み込む

TSX
// app/layout.tsx

import type { Metadata } from 'next';
import Header from '@/components/Header';
import Footer from '@/components/Footer';

export const metadata: Metadata = {
  title: 'My Blog',
  description: 'Next.jsで作ったブログです。',
};

export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <html lang="ja">
      <body>
        <Header />
        <main style={{ maxWidth: '800px', margin: '0 auto', padding: '2rem' }}>
          {children}
        </main>
        <Footer />
      </body>
    </html>
  );
}

💡 @/ について:

@/components/Header@/ はプロジェクトルートを指すエイリアスです。

../../components/Header のような相対パスより読みやすく書けます。

create-next-app で作ったプロジェクトなら最初から使えます。

これだけで、すべてのページにヘッダーとフッターが表示されるようになります。

ネストしたレイアウト

layout.tsx はどのフォルダにも置けます。

たとえば、ブログページだけ別のレイアウトを使いたい場合はこうします。

Bash
app/
├── layout.tsx          全体のレイアウト(Header・Footer)
└── blog/
    ├── layout.tsx      ブログセクション専用のレイアウト
    ├── page.tsx
    └── [id]/
        └── page.tsx
TSX
// app/blog/layout.tsx

export default function BlogLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <div>
      <aside style={{ marginBottom: '1.5rem', padding: '1rem', background: '#f9f9f9' }}>
        <p>📝 ブログセクション</p>
      </aside>
      {children}
    </div>
  );
}

レイアウトはネストして重なります。

/blog 以下のページは、ルートレイアウト(Header・Footer)の中に、このブログレイアウトがさらに入る形になります。

Bash
RootLayout(Header + Footer)
  └── BlogLayout(サイドバーなど)
        └── page.tsx の内容

Server ComponentとClient Component

ここからがこの記事のもっとも重要なテーマです。

Next.jsのApp Routerでは、コンポーネントはデフォルトでServer Componentとして動作します。

Server Component(デフォルト)

TSX
// Server Component("use client" がないので自動的にこちら)

export default function BlogPost() {
  return <article>記事の内容</article>;
}

Server Componentはサーバー上でHTMLを生成して、ブラウザに送ります

ブラウザ側でJavaScriptとして実行されません。

できること:

  • データベースやAPIから直接データを取得する(次の記事で詳しく扱います)
  • 軽量:ブラウザに余計なJavaScriptを送らない

できないこと:

  • useStateuseEffect などのReact Hooksが使えない
  • ボタンクリックなどのイベントを扱えない
  • windowdocument などブラウザのAPIにアクセスできない

Client Component

ファイルの先頭に "use client" と書くと、そのコンポーネントはClient Componentになります。

TSX
"use client";

import { useState } from 'react';

export default function Counter() {
  const [count, setCount] = useState(0);

  return (
    <div>
      <p>カウント:{count}</p>
      <button onClick={() => setCount(count + 1)}>+1</button>
    </div>
  );
}

Client Componentはブラウザ上でJavaScriptとして動きます。useStateonClick が使えるのはこちらです。

どちらを使えばいいの?

まずServer Componentで考えて、必要になったらClient Componentにするというのが基本方針です。

やりたいこと使うべきコンポーネント
データを取得して表示するServer Component
ページのタイトルや静的なテキストを表示するServer Component
ボタンを押したら何かが起きるClient Component
フォームの入力値を管理するClient Component
useState / useEffect を使うClient Component

実践:ハンバーガーメニューを作る

理解を深めるために、モバイルで開閉するハンバーガーメニューを作ってみましょう。

「クリックしたら開く」という動作が必要なので、Client Componentです。

TSX
// components/MobileMenu.tsx
"use client";

import { useState } from 'react';
import Link from 'next/link';

export default function MobileMenu() {
  const [isOpen, setIsOpen] = useState(false);

  return (
    <div>
      <button onClick={() => setIsOpen(!isOpen)}>
        {isOpen ? '✕ 閉じる' : '☰ メニュー'}
      </button>

      {isOpen && (
        <nav style={{ display: 'flex', flexDirection: 'column', gap: '0.5rem', marginTop: '0.5rem' }}>
          <Link href="/" onClick={() => setIsOpen(false)}>トップ</Link>
          <Link href="/blog" onClick={() => setIsOpen(false)}>ブログ</Link>
          <Link href="/about" onClick={() => setIsOpen(false)}>About</Link>
        </nav>
      )}
    </div>
  );
}

Header.tsx はServer Componentのままにして、その中にこの Client Component を読み込むことができます。

TSX
// components/Header.tsx(Server Component のまま)

import Link from 'next/link';
import MobileMenu from './MobileMenu'; // Client Component を読み込む

export default function Header() {
  return (
    <header style={{ borderBottom: '1px solid #eee', padding: '1rem 2rem' }}>
      {/* デスクトップ用ナビ */}
      <nav style={{ display: 'flex', gap: '1.5rem' }}>
        <Link href="/" style={{ fontWeight: 'bold' }}>My Blog</Link>
        <Link href="/blog">ブログ</Link>
        <Link href="/about">About</Link>
      </nav>

      {/* モバイル用メニュー */}
      <MobileMenu />
    </header>
  );
}

ポイント: Server ComponentはClient Componentを子として持てます。

逆(Client ComponentがServer Componentを子として持つ)は原則できません。

「外側がServer、インタラクティブな部分だけClient」という構造が理想的です。

ここまでの全体像

Bash
app/
├── layout.tsx             RootLayout(Header・Footer を含む)
└── blog/
    ├── layout.tsx         BlogLayout(ブログ専用)
    ├── page.tsx
    └── [id]/
        └── page.tsx

components/
├── Header.tsx             Server Component
├── Footer.tsx             Server Component
└── MobileMenu.tsx         Client Component("use client")

まとめ

この記事で学んだこと:

  • layout.tsx を使うと、全ページ共通のヘッダー・フッターを一か所で管理できる
  • レイアウトはネストできる。フォルダごとに専用レイアウトを設定可能
  • Server Component(デフォルト)はサーバーで動き、データ取得に向いている
  • Client Component"use client")はブラウザで動き、インタラクションに使う
  • 「まずServer Component、必要ならClient Component」 が基本方針

次の記事では、Server Componentの強みを活かして、外部APIからデータを取得して表示する方法を学びます。