LoginSignup
11
10

More than 1 year has passed since last update.

Next.js CSR / SSR / SSG / ISR の実装方法

Last updated at Posted at 2023-07-09

はじめに

Next.js では、2023年7月時点で以下2つのルーティング方法があります。

  • App Router:Next.js 13から利用可能。現在はこちらが推奨。
  • Pages Router:Next.js 13より前はこちらが主流。

App Router と Pages Router のどちらを利用するかによって、レンダリング手法等その他機能の実装も変わってきます。

App Router と Pages Router のレンダリングの違いを整理したいので、まずこの記事では、Pages Router で利用できる以下4つのレンダリングの実装方法について学んでいきたいと思います。

  • Client-Side Rendering (CSR)
  • Server-Side Rendering (SSR)
  • Static-Site Generation (SSG)
  • Incremental Static Regeneration (ISR)

※各レンダリング手法の仕組みやメリット・デメリット、ユースケースなどについては、別途こちらの記事でまとめています。

開発環境

開発環境は以下の通りです。

  • Windows11
  • VSCode
  • Next.js 13.4.7
  • React 18.2.0
  • TypeScript 5.1.3

プロジェクト作成

まずは以下のコマンドを実行して、プロジェクトを作成します。

npx create-next-app@latest

インストール中にプロンプトが表示されるので、それぞれ以下の値を入力・選択します。
image.png

今回は Pages Router を使いたいので、Would you like to use App Router? (recommended)No を選択します。それ以外は、デフォルト値通りにします。

インストールが完了するとプロジェクトファイルが作成されます。
image.png

以下のコマンドでローカルサーバーを起動し、http://localhost:3000 にアクセスするとアプリが表示されます。

npm run dev

image.png

実装するサンプルアプリ

以下のAPIから取得したデータを表示するアプリを作成します。

関連ページ・コンポーネントの追加

pages フォルダ以下に各レンダリング手法の名前をつけたページを追加します。

image.png

次に src/pages/index.tsx を書き換えて、先ほど追加した各ページに遷移できるようにします。

src/pages/index.tsx
import { Inter } from "next/font/google";
import { TITLE as TITLE_CSR } from "./client-side-rendering";
import { TITLE as TITLE_SSR } from "./server-side-rendering";
import { TITLE as TITLE_SSG } from "./static-site-generation";
import { TITLE as TITLE_ISR } from "./incremental-static-regeneration";
import Link from "next/link";

const inter = Inter({ subsets: ["latin"] });

export default function Home() {
  return (
    <main
      className={`flex min-h-screen flex-col items-center justify-between p-24 ${inter.className}`}
    >
      <h1>Next.js Pages Router</h1>

      <div className="mb-32 grid text-center lg:mb-0 lg:grid-cols-4 lg:text-left">
        <Link
          href="/client-side-rendering"
          className="group rounded-lg border border-transparent px-5 py-4 transition-colors hover:border-gray-300 hover:bg-gray-100 hover:dark:border-neutral-700 hover:dark:bg-neutral-800/30"
          rel="noopener noreferrer"
        >
          <h2 className={`mb-3 text-2xl font-semibold`}>
            {TITLE_CSR}{" "}
            <span className="inline-block transition-transform group-hover:translate-x-1 motion-reduce:transform-none">
              -&gt;
            </span>
          </h2>
        </Link>

        <Link
          href="/server-side-rendering"
          className="group rounded-lg border border-transparent px-5 py-4 transition-colors hover:border-gray-300 hover:bg-gray-100 hover:dark:border-neutral-700 hover:dark:bg-neutral-800/30"
          rel="noopener noreferrer"
        >
          <h2 className={`mb-3 text-2xl font-semibold`}>
            {TITLE_SSR}{" "}
            <span className="inline-block transition-transform group-hover:translate-x-1 motion-reduce:transform-none">
              -&gt;
            </span>
          </h2>
        </Link>

        <Link
          href="/static-site-generation"
          className="group rounded-lg border border-transparent px-5 py-4 transition-colors hover:border-gray-300 hover:bg-gray-100 hover:dark:border-neutral-700 hover:dark:bg-neutral-800/30"
          rel="noopener noreferrer"
        >
          <h2 className={`mb-3 text-2xl font-semibold`}>
            {TITLE_SSG}{" "}
            <span className="inline-block transition-transform group-hover:translate-x-1 motion-reduce:transform-none">
              -&gt;
            </span>
          </h2>
        </Link>

        <Link
          href="/incremental-static-regeneration"
          className="group rounded-lg border border-transparent px-5 py-4 transition-colors hover:border-gray-300 hover:bg-gray-100 hover:dark:border-neutral-700 hover:dark:bg-neutral-800/30"
          rel="noopener noreferrer"
        >
          <h2 className={`mb-3 text-2xl font-semibold`}>
            {TITLE_ISR}{" "}
            <span className="inline-block transition-transform group-hover:translate-x-1 motion-reduce:transform-none">
              -&gt;
            </span>
          </h2>
        </Link>
      </div>
    </main>
  );
}

image.png

最後にAPIから取得したデータを表示するコンポーネントを追加します。
このコンポーネントは全てのページで利用します。

FetchedRandomJoke.tsx
import { RandomJoke } from "@/types";

export function FetchedRandomJoke({
  randomJoke,
}: {
  randomJoke: RandomJoke | undefined;
}) {
  if (!randomJoke) return <p>Loading...</p>;

  return (
    <>
      <hr />
      <p>Fetched Data from Random Joke API</p>
      <ul>
        <li>Type: {randomJoke.type}</li>
        <li>Setup: {randomJoke.setup}</li>
        <li>Punchline: {randomJoke?.punchline}</li>
      </ul>
    </>
  );
}

Client-Side Rendering (CSR) の実装

データ取得ライブラリである SWR を利用してAPIからデータを取得します。

client-side-rendering.tsx
import { FetchedRandomJoke } from "@/components/FetchedRandomJoke";
import { SAMPLE_API_ENDPOINT } from "@/pages/api";
import { RandomJoke } from "@/types";
import Head from "next/head";
import useSWR from "swr";

export const TITLE = "Client-Side Rendering (CSR)";

export default function ClientSideRendering() {
  const { data: randomJoke } = useSWR<RandomJoke>(SAMPLE_API_ENDPOINT, fetcher);

  return (
    <>
      <Head>
        <title>{TITLE}</title>
      </Head>
      <h1>{TITLE}</h1>
      <p>Data fetched on the client-side only.</p>
      <FetchedRandomJoke randomJoke={randomJoke} />
    </>
  );
}

async function fetcher() {
  const response = await fetch(SAMPLE_API_ENDPOINT);
  const randomJoke = await response.json();

  return randomJoke;
}

Server-Side Rendering (SSR) の実装

getServerSideProps という非同期関数を利用してAPIからデータを取得します。

server-side-rendering.tsx
import Head from "next/head";
import { SAMPLE_API_ENDPOINT } from "./api";
import { FetchedRandomJoke } from "@/components/FetchedRandomJoke";
import { RandomJoke } from "@/types";

export const TITLE = "Server-Side Rendering (SSR)";

export default function ServerSideRendering({
  randomJoke,
}: {
  randomJoke: RandomJoke;
}) {
  return (
    <>
      <Head>
        <title>{TITLE}</title>
      </Head>
      <h1>{TITLE}</h1>
      <p>
        Data fetched on the server-side at <b>each</b> request before sending to
        the client.
      </p>
      <FetchedRandomJoke randomJoke={randomJoke} />
    </>
  );
}

export async function getServerSideProps() {
  const response = await fetch(SAMPLE_API_ENDPOINT);
  const randomJoke = await response.json();

  return {
    props: {
      randomJoke, // will be passed to the page component as props
    },
  };
}

Static-Site Generation (SSG) の実装

getStaticProps という非同期関数を利用してAPIからデータを取得します。

static-site-generation.tsx
import Head from "next/head";
import { title } from "process";
import { SAMPLE_API_ENDPOINT } from "./api";
import { RandomJoke } from "@/types";
import { FetchedRandomJoke } from "@/components/FetchedRandomJoke";

export const TITLE = "Static-Site Generation (SSG)";

export default function StaticSiteGeneration({
  randomJoke,
}: {
  randomJoke: RandomJoke;
}) {
  return (
    <>
      <Head>
        <title>{TITLE}</title>
      </Head>
      <h1>{TITLE}</h1>
      <p>
        Data fetched at <b>build-time</b> on the server-side before sending to
        the client.
      </p>
      <FetchedRandomJoke randomJoke={randomJoke} />
    </>
  );
}

// This function gets called at build time on server-side.
// It won't be called on client-side, so you can even do
// direct database queries.
export async function getStaticProps() {
  const response = await fetch(SAMPLE_API_ENDPOINT);
  const randomJoke = await response.json();

  return {
    props: {
      randomJoke, // will be passed to the page component as props
    },
  };
}

Incremental Static Regeneration (ISR) の実装

SSG と同じく getStaticProps という非同期関数を利用してAPIからデータを取得します。ISR の場合、この関数に revalidate という props を追加します。

incremental-static-regeneration.tsx
import Head from "next/head";
import { SAMPLE_API_ENDPOINT } from "./api";
import { RandomJoke } from "@/types";
import { FetchedRandomJoke } from "@/components/FetchedRandomJoke";

export const TITLE = "Incremental Static Regeneration (ISR)";

export default function IncrementalStaticRegeneration({
  randomJoke,
}: {
  randomJoke: RandomJoke;
}) {
  return (
    <>
      <Head>
        <title>{TITLE}</title>
      </Head>
      <h1>{TITLE}</h1>
      <p>
        Data fetched at build-time on the server-side and rebuilt when data
        updated.
      </p>
      <FetchedRandomJoke randomJoke={randomJoke} />
    </>
  );
}

// This function gets called at build time on server-side.
// It may be called again, on a serverless function, if
// revalidation is enabled and a new request comes in
export async function getStaticProps() {
  const response = await fetch(SAMPLE_API_ENDPOINT);
  const randomJoke = await response.json();

  return {
    props: {
      randomJoke, // will be passed to the page component as props
    },

    // Next.js will attempt to re-generate the page:
    // - When a request comes in
    // - At most once every 5 seconds
    revalidate: 5, // In seconds
  };
}

最後に

4種類実装できたので、動作確認をします。

localhost_3000-Google-Chrome-2023-07-09-15-59-58.gif

参考

11
10
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
11
10