0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【Next.js 15入門②】RSC/RCC とレンダリング戦略(CSR/SSR/SSG/ISR)

Last updated at Posted at 2025-04-27

はじめに

本記事では、Next.js でのレンダリング手法(CSR/SSR/SSG/ISR)の違いや適用方法と、RSC(React Server Components)、RCC(React Client Components)について解説します。

※こちらの記事の続編になります

想定読者

  • React の基礎知識がある方
  • RSC や RCC を活用したパフォーマンス向上に興味がある方
  • Next.js のレンダリング戦略を学びたい方

本記事の目標

  1. RSCRCCの使い方を学ぶ
  2. CSR、SSR、SSG、ISRの違いを理解する

1. RSC と RCC の違い

Next.js 15 では、コンポーネントが「サーバーコンポーネント(RSC)」と「クライアントコンポーネント(RCC)」の 2 種類に分かれています。

サーバーコンポーネント(RSC)はサーバー側(例:Vercel 上や Node.js 環境)でレンダリングされるため、データ取得やパフォーマンスに優れています。
一方、クライアントコンポーネント(RCC)はブラウザ(例:Chrome や Safari など)で動作し、ユーザーの操作に応じた動的な処理に適しています。

RSC (React Server Components)

  • サーバー側で動作するコンポーネント
  • Next.js のコンポーネントは、基本的にデフォルトで RSC になる
  • RSC では React Hooks(例:useStateuseEffect)を使用するとエラーになる

RCC (React Client Components)

  • クライアント側(ブラウザ)で動作するコンポーネント
  • React Hooks を使いたい場合は、RCC として作成する
  • Next.js で RCC を定義するには、ファイル先頭に'use client'と書く必要がある
"use client";

import { useState } from "react";

const page = () => {
  const [count, setCount] = useState(0);

  console.log("client");

  return (
    <>
      <div>クライアントコンポーネント</div>
      <p>
        <button
          className="hover:cursor-pointer whitespace-pre"
          onClick={() => setCount((prev) => ++prev)}
        >
          count:{" "}
        </button>
        {count}
      </p>
    </>
  );
};

export default page;

2. RCC と RSC の組み合わせ

RCC と RSC は組み合わせて使用できます。

例えば、画面描画時に RSC でデータフェッチを行い、必要なデータを props で RCC に渡し、ユーザーアクション(クリックなど)の機能は RCC で実装する、といった役割分担が可能です。

なお、RCC 配下のコンポーネントは暗黙的に RCC として扱われます。

ex: RCC と RSC を組み合わせた画面構成

src/
├── app/
│   │── api/
│   │   └── menu/
│   │       └── route.ts
│   └── rsc/
│       └── page.tsx
└── components/
    └── Contents.tsx
api/menu/route.ts
import { NextResponse } from "next/server";

export const GET = () => {
  return NextResponse.json(["blog", "counter", "about", "contact"]);
};
api/rsc/page.tsx
import Contents from "@/components/Contents";

const page = async () => {
  const response = await fetch("http://localhost:3000/api/menu", {
    cache: "no-store",
  });
  const menuList: string[] = await response.json();

  return (
    <>
      <div className="bg-green-400">サーバーコンポーネント</div>
      <div className="flex flex-col md:flex-row">
        {/* サイドメニュー */}
        <ul className="w-full md:w-3/12 bg-green-200">
          {menuList.map((menu, index) => (
            <li
              key={index}
              className="hover:bg-green-300 cursor-pointer px-2 py-1 rounded"
            >
              {menu}
            </li>
          ))}
        </ul>

        {/* メインコンテンツ(RCC) */}
        <div className="w-full md:w-9/12">
          <Contents initCount={menuList.length} />
        </div>
      </div>
    </>
  );
};

export default page;
components/Contents.tsx
"use client";

import { useEffect, useState } from "react";

const Contents = ({ initCount }: { initCount: number }) => {
  const [count, setCount] = useState(0);

  useEffect(() => setCount(initCount), [initCount]);

  return (
    <div className="bg-orange-200">
      <p>クライアントコンポーネント</p>
      <br />
      <p className="pl-5">
        <button
          className="hover:cursor-pointer whitespace-pre"
          onClick={() => setCount((prev) => ++prev)}
        >
          Counter:{" "}
        </button>
        {count}
      </p>
    </div>
  );
};

export default Contents;

画面はこのようになります。
image-2.png

3. レンダリング

Next.js の App Router では、ページごとに CSR(Client-Side Rendering)、SSR(Server-Side Rendering)、SSG(Static Site Generation)、ISR(Incremental Static Regeneration)を柔軟に使い分けることができます。
ページ要件に応じて、これらのレンダリング手法を選択することで、パフォーマンスや UX を最適化できます。

注意
開発モード(npm run dev)では、すべてのページが SSR として動作します。
SSG や ISR の挙動を確認するには、本番ビルド(npm run build)と本番起動(npm start)が必要です。

CSR(Client-Side Rendering)

  • クライアント側で HTML を生成する
  • 動的にページを更新する
  • ユーザーインタラクションが多いページや、SEO が不要なページに適している

SSR(Server-Side Rendering)

  • サーバ側で HTML を生成する
  • 常に最新のデータを取得する
  • ユーザーごとに異なるコンテンツを表示するページや、SEO が重要なページに適している
ex: SSR で Dog API から犬の画像を取得

外部ドメインの画像を使用する場合は、next.config.ts で許可設定が必要です。

next.config.ts
import type { NextConfig } from "next";

const nextConfig: NextConfig = {
  images: {
    // 許可する外部ドメインのパターンを記載
    remotePatterns: [
      // Dog APIのURL
      {
        protocol: "https",
        hostname: "images.dog.ceo",
      },
    ],
  },
};

export default nextConfig;
SSRPage
import Image from "next/image";

export const dynamic = "force-dynamic"; // 明示的にSSRを指定

const SSRPage = async () => {
  const res = await fetch("https://dog.ceo/api/breeds/image/random", {
    cache: "no-store",
  });
  if (!res.ok) {
    throw new Error("Failed to fetch Dog data");
  }

  /** 犬の画像データ */
  const image: string = (await res.json()).message;
  /** タイムスタンプ */
  const timestamp = new Date().toLocaleString('ja-JP', { timeZone: 'Asia/Tokyo' });

  return (
    <div>
      <p>SSRで犬の画像を取得</p>
      <p>Timestamp: {timestamp}</p>
      <br />
      <Image src={image} alt="dog" width={500} height={500} />
    </div>
  );
};

export default SSRPage;

このページでリロードすると、タイムスタンプが更新され、新しい犬の画像が表示できます。

SSG(Static Site Generation)

  • ビルド時に静的 HTML を生成する
  • 変更頻度が低いページや、初期表示速度を重視するページに適している
ex: SSG で PokéAPI からピカチュウのデータ取得
SSGPage.tsx
import Image from "next/image";

export const dynamic = "force-static"; // 明示的にSSGを指定

type Pokemon = {
  name: string;
  sprites: {
    front_default: string;
  };
  types: {
    type: {
      name: string;
    };
  }[];
};

const SSGPage = async () => {
  const res = await fetch("https://pokeapi.co/api/v2/pokemon/pikachu");
  if (!res.ok) {
    throw new Error("Failed to fetch Pikachu data");
  }

  /** ポケモンデータ */
  const pokemon: Pokemon = await res.json();
  /** タイムスタンプ */
  const timestamp = new Date().toLocaleString('ja-JP', { timeZone: 'Asia/Tokyo' });

  return (
    <div>
      <p>SSGでピカチュウのデータ取得</p>
      <p>Timestamp: {timestamp}</p>
      <br />
      <p>名前:{pokemon.name}</p>
      <p>タイプ:</p>
      <ol>
        {pokemon.types.map((typeInfo) => (
          <li key={typeInfo.type.name}>{typeInfo.type.name}</li>
        ))}
      </ol>
      <Image
        src={pokemon.sprites.front_default}
        alt={pokemon.name}
        width={300}
      />
    </div>
  );
};

export default SSGPage;

ピカチュウのデータが取得できました。
このページで何度ブラウザを更新しても、タイムゾーンが変わらないことが確認できます。

image-1.png

ISR(Incremental Static Regeneration)

  • 一定時間ごとに静的ページを再生成する
  • 最新ではないが適度に更新したいページや、大量のコンテンツを扱う場合に適している
ex: ISR で 10 秒ごとに国際宇宙ステーション(ISS)の現在位置を取得
export type IssNowResponse = {
  timestamp: number;
  iss_position: {
    latitude: string;
    longitude: string;
  };
};

export const revalidate = 10; // 再生成の間隔を指定

const ISRPage = async () => {
  const res = await fetch("http://api.open-notify.org/iss-now.json", {
    next: { revalidate: 10 }, // 10秒ごとに再生成
  });
  if (!res.ok) {
    throw new Error("Failed to fetch ISS data");
  }

  const data: IssNowResponse = await res.json();

  return (
    <div style={{ textAlign: "center", margin: "2rem" }}>
      <h1>国際宇宙ステーションの現在位置</h1>
      <p>緯度: {data.iss_position.latitude}</p>
      <p>経度: {data.iss_position.longitude}</p>
      <p>最終更新日時: {new Date(data.timestamp * 1000).toISOString()}</p>
    </div>
  );
};

export default ISRPage;

10 秒後にブラウザを更新すると、ISS の緯度・経度と最終更新日時が更新されることが確認できます。

まとめ

本記事では、Next.js のレンダリング戦略とコンポーネントについて学びました。

〈コンポーネント〉

  • RSC (React Server Components): サーバー側で動作するコンポーネント。React Hooks は使えません。
  • RCC (React Client Components): クライアント側で動作するコンポーネント。'use client'を記述する必要があります。

〈レンダリング〉

  • CSR (Client-Side Rendering): クライアント側で HTML を生成。動的なページ更新に適しています。
  • SSR (Server-Side Rendering): サーバー側で HTML を生成。最新データや SEO が重要なページに適しています。
  • SSG (Static Site Generation): ビルド時に静的 HTML を生成。初期表示速度が重視されるページに適しています。
  • ISR (Incremental Static Regeneration): 定期的に静的ページを再生成。大量コンテンツや適度な更新が求められるページに適しています。

これらの戦略を状況に応じて使い分けることで、パフォーマンスや SEO を最適化できます。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?