【Next.js】共通コンポーネントでヘッダーとフッターを作成する|簡単なToDoアプリの作成

Next.jsで、どのページにも共通で表示されるヘッダーとフッターを作成する方法を解説します。コンポーネントという部品単位で作り、`layout.tsx`に配置することでサイト全体のレイアウトを効率的に管理します。ヘッダーには、PCとスマホで表示が切り替わるレスポンシブ対応のメニューも実装します。

作成日: 更新日:

開発環境

  • OS: Windows10
  • Visual Studio Code: 1.73.0
  • node: 22.14.0
  • react: 19.0.0
  • next: 15.3.2
  • tailwindcss: 4
  • Prisma: 6.8.2

サンプルコード

/src/app/components/Footer.tsx

/src/app/components/Footer.tsx
1+export default function Footer() {
2+    return (
3+        <footer className="bg-gray-800 text-white py-4">
4+            <div className="max-w-4xl mx-auto px-4 text-center text-sm">
5+                &copy; {new Date().getFullYear()} MyTodoApp. All rights reserved.
6+            </div>
7+        </footer>
8+    )
9+}
10

/src/app/components/Header.tsx

/src/app/components/Header.tsx
1+'use client'
2+
3+import { useState } from 'react'
4+import Link from 'next/link'
5+
6+export default function Header() {
7+    const [menuOpen, setMenuOpen] = useState(false)
8+
9+    return (
10+        <header className="bg-white shadow-md">
11+            <div className="max-w-4xl mx-auto px-4 py-4 flex justify-between items-center">
12+                {/* ロゴ */}
13+                <Link href="/">
14+                    <span className="text-xl font-bold text-blue-600 hover:underline cursor-pointer">
15+                        MyAppLogo
16+                    </span>
17+                </Link>
18+
19+                {/* ハンバーガーアイコン(スマホ用) */}
20+                <button
21+                    className="md:hidden text-gray-700 focus:outline-none"
22+                    onClick={() => setMenuOpen(!menuOpen)}
23+                    aria-label="Toggle menu"
24+                >
25+                    <svg
26+                        className="w-6 h-6"
27+                        fill="none"
28+                        stroke="currentColor"
29+                        viewBox="0 0 24 24"
30+                    >
31+                        {menuOpen ? (
32+                            <path
33+                                strokeLinecap="round"
34+                                strokeLinejoin="round"
35+                                strokeWidth={2}
36+                                d="M6 18L18 6M6 6l12 12"
37+                            />
38+                        ) : (
39+                            <path
40+                                strokeLinecap="round"
41+                                strokeLinejoin="round"
42+                                strokeWidth={2}
43+                                d="M4 6h16M4 12h16M4 18h16"
44+                            />
45+                        )}
46+                    </svg>
47+                </button>
48+
49+                {/* メニュー(PC用) */}
50+                <nav className="hidden md:flex items-center space-x-4 md:space-x-6">
51+                    <Link href="/">
52+                        <span className="cursor-pointer px-3 py-1 hover:text-blue-600">
53+                            Menu1
54+                        </span>
55+                    </Link>
56+                    <Link href="/">
57+                        <span className="cursor-pointer px-3 py-1 hover:text-blue-600">
58+                            Menu2
59+                        </span>
60+                    </Link>
61+                    <Link href="/">
62+                        <span className="cursor-pointer px-3 py-1 hover:text-blue-600">
63+                            Menu3
64+                        </span>
65+                    </Link>
66+                </nav>
67+            </div>
68+
69+            {/* メニュー(スマホ用) */}
70+            {menuOpen && (
71+                <nav className="md:hidden px-4 pb-4 space-y-2 border-t border-gray-300 text-gray-700 font-medium">
72+                    <Link href="/">
73+                        <span className="block py-2 hover:text-blue-600 cursor-pointer">
74+                            Menu1
75+                        </span>
76+                    </Link>
77+                    <Link href="/">
78+                        <span className="block py-2 hover:text-blue-600 cursor-pointer">
79+                            Menu2
80+                        </span>
81+                    </Link>
82+                    <Link href="/">
83+                        <span className="block py-2 hover:text-blue-600 cursor-pointer">
84+                            Menu3
85+                        </span>
86+                    </Link>
87+                </nav>
88+            )}
89+        </header>
90+    )
91+}
92

/src/app/layout.tsx

/src/app/layout.tsx
1 import type { Metadata } from "next";
2 import { Geist, Geist_Mono } from "next/font/google";
3 import "./globals.css";
4+import Header from '@/app/components/Header'
5+import Footer from '@/app/components/Footer'
6 
7 const geistSans = Geist({
8   variable: "--font-geist-sans",
9       <body
10         className={`${geistSans.variable} ${geistMono.variable} antialiased`}
11       >
12-        {children}
13+        <Header />
14+          {children}
15+        <Footer />
16       </body>
17     </html>
18   );
19

コード解説

変更点: フッターコンポーネントの作成

/src/app/components/Footer.tsx
1+export default function Footer() {
2+    return (
3+        <footer className="bg-gray-800 text-white py-4">
4+            <div className="max-w-4xl mx-auto px-4 text-center text-sm">
5+                &copy; {new Date().getFullYear()} MyTodoApp. All rights reserved.
6+            </div>
7+        </footer>
8+    )
9+}

まず、サイトの最下部に表示されるフッター部分を、独立した「コンポーネント(部品)」として作成します。/src/app/components/ディレクトリにFooter.tsxという新しいファイルを追加しました。

export default function Footer() { ... }という記述は、「Footer」という名前のコンポーネントを定義し、他のファイルからこの部品をimportして使えるようにするための宣言です。

className="..."の部分は、Tailwind CSSというCSSフレームワークを使ってスタイルを適用しています。例えば、bg-gray-800は背景を濃い灰色に、text-whiteは文字色を白に設定します。

{new Date().getFullYear()}はJavaScriptのコードで、現在の西暦を動的に取得して表示します。これにより、年が新しくなっても自動でコピーライト表記が更新されます。

変更点: ヘッダーコンポーネントの作成

/src/app/components/Header.tsx
1+'use client'
2+
3+import { useState } from 'react'
4+import Link from 'next/link'
5+
6+export default function Header() {
7+    const [menuOpen, setMenuOpen] = useState(false)
8+
9+    return (
10+        <header className="bg-white shadow-md">
11+            <div className="max-w-4xl mx-auto px-4 py-4 flex justify-between items-center">
12+                {/* ロゴ */}
13+                <Link href="/">
14+                    <span className="text-xl font-bold text-blue-600 hover:underline cursor-pointer">
15+                        MyAppLogo
16+                    </span>
17+                </Link>
18+
19+                {/* ハンバーガーアイコン(スマホ用) */}
20+                <button
21+                    className="md:hidden text-gray-700 focus:outline-none"
22+                    onClick={() => setMenuOpen(!menuOpen)}
23+                    aria-label="Toggle menu"
24+                >
25+                    {/* ... icon svg ... */}
26+                </button>
27+
28+                {/* メニュー(PC用) */}
29+                <nav className="hidden md:flex items-center space-x-4 md:space-x-6">
30+                    {/* ... menu links ... */}
31+                </nav>
32+            </div>
33+
34+            {/* メニュー(スマホ用) */}
35+            {menuOpen && (
36+                <nav className="md:hidden px-4 pb-4 space-y-2 border-t border-gray-300 text-gray-700 font-medium">
37+                    {/* ... menu links ... */}
38+                </nav>
39+            )}
40+        </header>
41+    )
42+}

次に、サイト上部に表示されるヘッダーのコンポーネントを/src/app/components/Header.tsxとして作成します。このヘッダーには、PCとスマートフォンで表示が切り替わるレスポンシブデザインを実装します。

ファイルの先頭にある'use client'は、このコンポーネントがブラウザ側で動作する「クライアントコンポーネント」であることをNext.jsに伝えます。メニューの開閉など、ユーザーの操作に応じて表示が変わるインタラクティブな機能を持たせるために必要です。

const [menuOpen, setMenuOpen] = useState(false)は、ReactのuseStateという機能(フック)を使って、スマホ用メニューの開閉状態を管理しています。menuOpenが現在の状態(trueが開、falseが閉)、setMenuOpenが状態を更新するための関数です。ハンバーガーアイコンのonClickイベントでsetMenuOpen(!menuOpen)を呼び出し、状態を反転させています。

LinkコンポーネントはNext.jsが提供するページ遷移のための部品です。HTMLの<a>タグと似ていますが、ページ全体を再読み込みすることなく高速に画面を切り替えることができます。

PC用メニュー(<nav className="hidden md:flex ...">)とスマホ用ハンバーガーアイコン(<button className="md:hidden ...">)のclassNameに注目してください。md:hiddenは「中画面サイズ(md)以上で非表示」、hidden md:flexは「通常は非表示で、中画面サイズ以上で表示」という意味です。これにより、画面幅に応じて表示が自動で切り替わります。

スマホ用のメニュー部分は{menuOpen && ( ... )}という構文で囲まれており、menuOpenの状態がtrueのときだけ表示される仕組みになっています。

変更点: 作成したコンポーネントをサイト全体のレイアウトに適用

/src/app/layout.tsx
1 import type { Metadata } from "next";
2 import { Geist, Geist_Mono } from "next/font/google";
3 import "./globals.css";
4+import Header from '@/app/components/Header'
5+import Footer from '@/app/components/Footer'
6 
7 const geistSans = Geist({
8   variable: "--font-geist-sans",
9       <body
10         className={`${geistSans.variable} ${geistMono.variable} antialiased`}
11       >
12-        {children}
13+        <Header />
14+          {children}
15+        <Footer />
16       </body>
17     </html>
18   );

最後に、作成したヘッダーとフッターのコンポーネントを、サイト全体のレイアウトに組み込みます。この設定は、すべてのページに共通の骨格を提供する/src/app/layout.tsxファイルで行います。

まず、import文を使って、先ほど作成したHeaderコンポーネントとFooterコンポーネントを読み込みます。

次に、<body>タグの中身を変更します。もともとあった{children}を、<Header /><Footer />で上下から挟むように配置します。

{children}は、これから作成する各ページ(トップページや詳細ページなど)の本体コンテンツが挿入される場所を示す特別な変数です。

このようにlayout.tsx{children}を共通コンポーネントで囲むことで、アプリケーション内のどのページにアクセスしても、自動的にヘッダーが上部に、フッターが下部に表示されるようになります。サイト全体で一貫したデザインを効率的に管理するための、Next.jsの重要な仕組みです。

おわりに

今回は、ヘッダーとフッターを再利用可能な「コンポーネント」として作成し、サイト全体の設計図であるlayout.tsxに配置する方法を学びました。このlayout.tsxでページの本体である{children}を挟み込むことで、どのページにも共通のデザインを簡単に適用できます。また、ヘッダーに'use client'useStateを使ってメニューの開閉機能を実装したように、コンポーネントごとにインタラクティブな要素を追加することも可能です。この「部品化」と「共通レイアウト」の考え方は、Next.jsでの効率的な開発に欠かせない基本スキルです。

関連コンテンツ