【Next.js】コンポーネントの種類と使い分け

# はじめに
引き続きWebの開発を進めています。
Next.jsを使用しているのですが、実装していてコンポーネントについての理解が足りないなと感じる場面があり、深追いするために色々と調べてみました。
そこで今回は備忘録も兼ねて、コンポーネントについてまとめてみようと思います。

# 1. ページコンポーネント (Page Components)

Next.jsでは、各ページは**ページコンポーネント**として定義されます。

- `app`ディレクトリでは、各ページは`page.tsx`として作成。
- `pages`ディレクトリを使用する場合は、デフォルトエクスポート。

```page.tsx
export default function Home() {
  return <h1>Home Page</h1>;
}
```

# 2. レイアウトコンポーネント (Layout Components)

ページ全体の共通レイアウトを定義するためのコンポーネントです。  
ヘッダーやフッター、ナビゲーションなど、すべてのページで共通して適用される要素を管理するのに使用します。

### **App Router (`app/layout.tsx`) の場合**
Next.js 13以降のApp Routerでは、`layout.tsx` を使用してレイアウトを作成します。

```layout.tsx
export default function Layout({ children }: { children: React.ReactNode }) {
  return (
    <div>
      <header>Header</header>
      <main>{children}</main>
      <footer>Footer</footer>
    </div>
  );
}
```

### **Pages Router (`pages/_app.tsx`) の場合**
`pages/_app.tsx` を使うと、すべてのページに共通のレイアウトを適用できます。

```_app.tsx
import type { AppProps } from "next/app";

export default function MyApp({ Component, pageProps }: AppProps) {
  return (
    <div>
      <header>Header</header>
      <main>
        <Component {...pageProps} />
      </main>
      <footer>Footer</footer>
    </div>
  );
}
```

レイアウトの特徴
- layout.tsx はページの切り替え時に状態を維持できる
- _app.tsx はPages Routerで全ページに適用される
- ヘッダー、フッター、サイドバーなどを統一できる

# 3. サーバーコンポーネント (Server Components) [App Router]

Next.js 13以降の**App Router (`app` ディレクトリ)** では、デフォルトでコンポーネントが**サーバーコンポーネント (Server Components)** として動作します。  
サーバーコンポーネントは**サーバー側でのみ実行**されるため、以下のような利点があります。

### **サーバーコンポーネントの特徴**
- **クライアント側にJavaScriptが送信されない**（より軽量）
- **データベースやAPIから直接データを取得できる**
- **非同期関数 (`async/await`) を直接使える**
- **SEOの最適化がしやすい**

### **サーバーコンポーネントの基本**
以下のように、`async` 関数としてAPIからデータを取得し、直接コンポーネントで利用できます。

```page.tsx
// app/dashboard/page.tsx
async function Dashboard() {
  const data = await fetch("https://api.example.com/data").then((res) => res.json());

  return (
    <div>
      <h1>Dashboard</h1>
      <p>{data.message}</p>
    </div>
  );
}

export default Dashboard;
```

### **クライアントコンポーネントとの違い**

サーバーコンポーネントは **`useState` や `useEffect` などのフックを使用できません**。  
なぜなら、サーバーコンポーネントはクライアント側で実行されないため、状態管理や副作用処理が不要だからです。  
もし、クライアントサイドで状態を管理したい場合は、**クライアントコンポーネントにする必要があります**。

### **NG例（サーバーコンポーネントで useState を使用）**
以下のコードはエラーになります。

```page.tsx
export default function ServerComponent() {
  const [count, setCount] = useState(0); // ❌ エラー
  return <button onClick={() => setCount(count + 1)}>Count: {count}</button>;
}
```

# 4. クライアントコンポーネント (Client Components) [App Router]

Next.js 13以降の**App Router (`app` ディレクトリ)** では、デフォルトで**サーバーコンポーネント**として動作します。  
しかし、`"use client"` を追加することで、クライアントコンポーネントとして動作させることができます。

### **クライアントコンポーネントの特徴**
- **`useState` や `useEffect` などのReactフックを使用できる**
- **イベントハンドリング（クリックやフォーム入力など）が可能**
- **ブラウザのAPI（`window`, `localStorage` など）を使用できる**
- **サーバーコンポーネントの中で組み込んで使うことができる**

## **クライアントコンポーネントの基本**

クライアントコンポーネントを作成するには、**ファイルの先頭に `"use client"` を記述** します。

```test.tsx
"use client"; // クライアントコンポーネントにはこれが必須

import { useState } from "react";

export default function Counter() {
  const [count, setCount] = useState(0);
  
  return (
    <button onClick={() => setCount(count + 1)}>Count: {count}</button>
  );
}
```

# 5. ダイナミックコンポーネント (Dynamic Components)

Next.jsでは、**ダイナミックインポート**を使用することで、コンポーネントの遅延読み込み（Lazy Loading）が可能です。  
これにより、**不要なJavaScriptのロードを防ぎ、パフォーマンスを向上**させることができます。

## **ダイナミックコンポーネントの特徴**
- **初回レンダリング時に不要なコンポーネントを読み込まない**
- **ページロードを高速化できる**
- **SSR (サーバーサイドレンダリング) を無効化できる**
- **ユーザーアクションに応じてコンポーネントを読み込める**

## **ダイナミックコンポーネントの基本**
Next.jsの `next/dynamic` を使用して、コンポーネントを遅延読み込みできます。

```dynamic.tsx
import dynamic from "next/dynamic";

// ダイナミックインポート
const HeavyComponent = dynamic(() => import("@/components/HeavyComponent"));

export default function Page() {
  return (
    <div>
      <h1>ダイナミックコンポーネントの例</h1>
      <HeavyComponent />
    </div>
  );
}
```

# 6. エラーバウンドリーコンポーネント (Error Boundary Components) [App Router]

Next.jsの**エラーバウンドリーコンポーネント**は、ページやコンポーネントで発生した**ランタイムエラーをキャッチし、適切に処理**するための仕組みです。  
`app` ディレクトリを使用する**App Router**では、エラーバウンドリーとして `error.tsx` を作成することで、エラー発生時のUIをカスタマイズできます。

## **エラーバウンドリーの特徴**
- **コンポーネント内で発生したエラーをキャッチできる**
- **ユーザーに適切なエラーメッセージを表示できる**
- **ページごとにエラーハンドリングを設定できる**
- **「再試行ボタン」などのカスタムUIを提供できる**

## **エラーバウンドリーの基本**
Next.jsの `app` ディレクトリでは、ページやレイアウトのエラーをキャッチするために、**`error.tsx` を作成**します。

### **エラーバウンドリーの基本的な構成**
```error.tsx
"use client"; // クライアントコンポーネントとして動作させる必要がある

export default function Error({ error, reset }: { error: Error; reset: () => void }) {
  return (
    <div>
      <h2>エラーが発生しました</h2>
      <p>{error.message}</p>
      <button onClick={() => reset()}>再試行</button>
    </div>
  );
}
```

# 7. ローディングコンポーネント (Loading Components) [App Router]

Next.jsの**ローディングコンポーネント**を使用すると、**ページやレイアウトのデータが読み込まれるまでの間にローディングUIを表示**できます。  
`app` ディレクトリを使用する**App Router**では、`loading.tsx` を作成することで、ローディング中の状態を簡単に制御できます。

## **ローディングコンポーネントの特徴**
- **ページ遷移時にローディングアニメーションを表示**
- **サーバーコンポーネントでデータ取得中にスケルトンスクリーンを表示**
- **ページごと、またはレイアウト全体に適用可能**
- **自動的に適用され、追加のコードは不要**

## **基本的な `loading.tsx` の作成**
Next.jsの `app` ディレクトリでは、**ページやレイアウトと同じディレクトリに `loading.tsx` を作成**すると、そのページやレイアウトのローディング時に自動で表示されます。

### **`app/loading.tsx`（全ページ共通のローディングUI）**
```loading.tsx
export default function Loading() {
  return <p>Loading...</p>;
}
```

# 8. テンプレートコンポーネント (Template Components) [App Router]

Next.jsの**テンプレートコンポーネント (Template Components)** を使用すると、  
**ページ遷移時にレイアウトを維持しつつ、一部のコンテンツのみを再レンダリングする**ことが可能になります。  
これは、Reactの `React.memo` のような最適化をNext.jsのレベルで実現する機能です。

## **テンプレートコンポーネントの特徴**
- **ページ遷移時にレイアウトを再レンダリングせずに維持できる**
- **レイアウトの状態 (スクロール位置や開いたメニューなど) を保持可能**
- **ページごとに異なる `template.tsx` を設定できる**
- **`layout.tsx` とは異なり、子コンポーネントの更新を最適化できる**

## **テンプレートコンポーネントの基本**
Next.jsの `app` ディレクトリでは、**`template.tsx` を作成**することで、テンプレートコンポーネントを適用できます。

配置例：
**`app/dashboard/template.tsx`**
```template.tsx
export default function DashboardTemplate({ children }: { children: React.ReactNode }) {
  return (
    <div className="dashboard-template">
      <h1>ダッシュボード</h1>
      {children}
    </div>
  );
}
```

# 9. ミドルウェアコンポーネント (Middleware Components)

Next.jsの**ミドルウェアコンポーネント (Middleware Components)** は、  
**リクエストとレスポンスの間で処理を行い、リクエストのリダイレクトや認証チェックを実装する機能**です。  
`middleware.ts` を作成することで、グローバルまたは特定のルートに適用できます。

## **ミドルウェアの特徴**
- **リクエストがサーバーに到達する前に処理を実行できる**
- **認証チェックやリダイレクトを実装できる**
- **APIエンドポイントやページのアクセス制御が可能**
- **Edge Functions を活用して超高速に動作**

## **ミドルウェアの基本**
Next.jsでミドルウェアを使用するには、**プロジェクトのルートに `middleware.ts` を作成**します。

```middleware.tsx
import { NextResponse } from "next/server";
import type { NextRequest } from "next/server";

// ミドルウェアの処理
export function middleware(req: NextRequest) {
  const token = req.cookies.get("auth-token");

  // トークンがない場合はログインページへリダイレクト
  if (!token) {
    return NextResponse.redirect(new URL("/login", req.url));
  }

  return NextResponse.next();
}
```

# **まとめ**

Next.jsには、さまざまな種類のコンポーネントがあり、それぞれ異なる用途で活用できます。  
適切に使い分けることで、**アプリのパフォーマンスやUXを最適化**できます。

## **各コンポーネントの用途一覧**
| コンポーネント名 | 役割 |
|------------------|----------------------------|
| **ページコンポーネント** | 各ページ (`page.tsx`) のルートコンポーネント |
| **レイアウトコンポーネント** | 共通レイアウト (`layout.tsx`) を定義 |
| **サーバーコンポーネント** | サーバーサイドでデータを取得＆レンダリング |
| **クライアントコンポーネント** | クライアントサイドで状態管理やイベント処理 |
| **ダイナミックコンポーネント** | 必要なときにのみコンポーネントを読み込む |
| **エラーバウンドリー** | エラー発生時に適切な処理を行う (`error.tsx`) |
| **ローディングコンポーネント** | データ取得中のUI (`loading.tsx`) を制御 |
| **テンプレートコンポーネント** | ページ遷移時にレイアウトを維持 (`template.tsx`) |
| **ミドルウェア** | リクエスト時に認証・リダイレクトなどを処理 |

## **どのコンポーネントをどのタイミングで使うべきか？**
| 目的 | 適切なコンポーネント |
|------|-----------------|
| **ページごとのルートコンポーネント** | ページコンポーネント (`page.tsx`) |
| **全ページ共通のヘッダー・フッター** | レイアウトコンポーネント (`layout.tsx`) |
| **データ取得 & SEO対策** | サーバーコンポーネント |
| **ユーザーの操作 (クリック・入力)** | クライアントコンポーネント |
| **初回ロードを速くする** | ダイナミックコンポーネント |
| **エラーハンドリングを統一** | エラーバウンドリー |
| **データ取得中のUIを表示** | ローディングコンポーネント |
| **ページ遷移時にレイアウトを維持** | テンプレートコンポーネント |
| **認証チェック & リダイレクト** | ミドルウェア |

## **まとめ**
- **Next.jsには、サーバーコンポーネント・クライアントコンポーネントの区別がある**
- **ページの共通レイアウトには `layout.tsx` を使用**
- **エラーやローディングのUIをカスタマイズできる (`error.tsx`, `loading.tsx`)**
- **ミドルウェアを使うことで、リクエスト時に認証やリダイレクトを処理**
- **適切なコンポーネントを選ぶことで、パフォーマンスとUXを向上を期待できる**

# さいごに
何とか減量目標の3分の1を達成しました、この調子で健康的な生活を取り戻します！