🧐

Next.jsの重要概念を理解する【CSR,SSR,SSG編】

に公開

CSR

CSRは「クライアントサイドレンダリング」と呼ばれ、ブラウザ側でページを構築する方式です。
ユーザーがページにアクセスすると、サーバーは最低限のHTML(ほぼ空)を返し、ブラウザはそのHTMLを読み込み、さらにJavaScriptをダウンロードして初めて画面が描画されます。

  • 仕組み
    ・サーバーからほぼ空のHTMLが送られる
    ・ブラウザがHTMLを読み込み、JavaScriptを実行して画面を構築
    ・JavaScriptの読み込み後は、ページ遷移が高速

  • 特徴
    初期表示が遅め(JS読み込みまで何も表示されない)
    ・一度JSが読み込まれれば、2回目以降のページ遷移は非常に速い

  • CSRにする方法
    Next.jsでは、use client をファイルの先頭に書くことで、そのコンポーネントがクライアントコンポーネント(= CSR)として扱われます。
    この宣言があることで、useStateuseEffectなど、クライアント限定のReact機能が使えるようになります。

'use client'
import React, { useState, useEffect } from 'react'
 
export function Page() {
  const [data, setData] = useState(null)
 
  useEffect(() => {
    const fetchData = async () => {
      const response = await fetch('https://api.example.com/data')
      if (!response.ok) {
        throw new Error(`HTTP error! status: ${response.status}`)
      }
      const result = await response.json()
      setData(result)
    }
 
    fetchData().catch((e) => {
      // handle the error as needed
      console.error('An error occurred while fetching the data: ', e)
    })
  }, [])
 
  return <p>{data ? `Your data: ${data}` : 'Loading...'}</p>
}
  • CSRを使うべき場面
    主にインタラクティブ性の高いページで使います。
    たとえば、Google Mapsのようにマウス操作や動的な表示が頻繁に発生するUIではCSRが適しています。
    useStateuseEffectなどのフックはサーバー側では使えず、use clientを宣言して初めて使えるため、個人的には「インタラクティブなコンポーネントは自然とCSRになる」という理解をしています。

SSR

Next.jsの「プリレンダリング機能」の一つがSSR(サーバーサイドレンダリング)です。
これは、ページにアクセスされたタイミングでHTMLをサーバーが生成し、ブラウザに送る方式です。

  • 仕組み
    ・ページへのリクエストごとに、サーバーがHTMLを生成
    ・ブラウザはそのHTMLを受け取り、JSが適用される。

  • 特徴
    初期表示(HTML)は速い
    ・JSは後から適用されるため、完全な動作まで少し時間がかかる
    ・毎回HTMLを生成するため、常に最新の情報を表示できる

初期表示が早いというのは、HTMLの静的な部分に限ります。HTMLの部分が高速で表示されている間に、JSの動的な部分が順次表示されていくイメージです。

  • SSRにする方法
    ・fetch関数の第二引数にno-storeを指定するなど(他にも方法はあるが、長くなるので今回は取り扱わない。)
// SSRにする
const res = await fetch(url, { cache: 'no-store' })

SSG

プリレンダリングのもう一つの方式がSSGです。
SSGでは、ビルド時にHTMLが生成されるため、非常に高速にページを表示できます。

  • 仕組み
    ・next build 時にHTMLを生成(1回だけ)
    ・ユーザーのリクエストごとに、生成済みHTMLを返すだけ

  • 特徴
    ・表示速度が圧倒的に速い
    ・サーバー処理が不要なのでスケーラビリティが高い
    ただし、ビルド時に内容が確定しているページに限る

  • SSGにする方法
    ・fetch関数の第2引数で { cache: 'force-cache' }(デフォルト)を使用
    何も指定しなければ基本的にSSG扱いになる

  • 向いている場面
    ・ランディングページや、更新頻度の少ないコンテンツ
    ・商品一覧、ブログ記事など、事前に内容が確定しているページ

SSRをSSGにできるときがある

基本的に、URLにidなどの動的パラメータを含むページは、ビルド時に内容が確定しないためSSR(サーバーサイドレンダリング)が適していると思われがちです。
しかし、実はSSG(静的サイト生成)にできるケースもあります。
極端な例を挙げると、ブログサイトでアカウントページのURLが連番(例:/user/1, /user/2)で構成されている場合です。このように、事前に必要なパラメータ(ID一覧)が分かっていれば、ビルド時に静的にルートを生成できます。

そのためには、Next.jsのgenerateStaticParams関数(AppRouter専用)を使います。これにより、どのパスを静的にプリレンダリングするかを Next.jsに伝えることができます。

SSRバージョン(動的)

以下のように cache: 'no-store' を指定すると、ページはリクエストごとに生成される SSR になります。

// app/products/[id]/page.js

import { notFound } from 'next/navigation'

export default async function ProductPage({ params }) {
  const res = await fetch(`https://api.example.com/products/${params.id}`, {
    cache: 'no-store', // SSRになる
  })

  if (!res.ok) return notFound()

  const product = await res.json()

  return <h1>{product.name}</h1>
}

SSGバージョン

上記のページは、以下のように書き換えることで SSGに変えることができます。

// app/products/[id]/page.js

import { notFound } from 'next/navigation'

// ビルド時にプリレンダリングするパスを指定
export async function generateStaticParams() {
  const res = await fetch('https://api.example.com/products')
  const products = await res.json()

  return products.map(product => ({
    id: product.id,
  }))
}

export default async function ProductPage({ params }) {
  const res = await fetch(`https://api.example.com/products/${params.id}`) // デフォルトのキャッシュ(SSG)

  if (!res.ok) return notFound()

  const product = await res.json()

  return <h1>{product.name}</h1>
}


generateStaticParamsを使うと、Next.js はビルド時に指定された全てのパスでページを事前生成します。
fetchに cache: 'no-store' を指定しなければ、Next.js は自動的に静的キャッシュ(SSG)扱いにします。
・IDが限定的かつ事前に取得できる場合は、SSRよりSSGの方が表示速度・スケーラビリティに優れています。

使い分けまとめ

個人的な認識だと「基本SSGで、ビルド時にデータが確定できない場合はSSRにし、クライアント側の状態管理やユーザー操作が必要な部分はCSR(クライアントコンポーネント)にする」です。
SSG > SSR > CSRの順番で検討してみるのがいいです。
そしてページの中でもコンポーネント単位で使い分けていくのが最適だと思います。

使用方式 特徴 適した場面
SSG 表示が最速。ビルド時に内容が確定している必要がある。 ランディングページ、商品一覧などの静的なページ
SSR 表示は速いが、リクエストごとにHTMLをサーバーで生成。 ログインユーザーのダッシュボード、検索結果など内容が動的なページ
CSR 初期表示は遅めだが、クライアント側で完全に動作する。 Google Maps、チャットUI、ゲームなど高インタラクティブなページ

また、SSGかSSRかはnpm run buildでビルドするとわかります。
「〇」がSSGで「f」がSSRです。残念ながらCSRかどうかはこの方法でわからないようです。

最後に

今回あらためて、CSR・SSR・SSGの3つのレンダリング方式について整理してみました。
当初はISRやPPRも含めて網羅したかったのですが、ボリュームが多くなりすぎるため、今回は概要に絞っています。

また、CSRとSSRのパフォーマンスの違いも実際に比較してみたのですが、思ったより明確な差が出ず、少し苦戦しました。
一見CSRの方が速く感じる場面もありましたが、通信速度を3G相当に落として検証すると、やはりSSRの方が初期表示が早くなる傾向がありました。特に大規模な画面になってくると、SSRの初速の優位性がより顕著になりそうだと感じました。

現在僕がアサインしているプロジェクトでは、主にフォーム中心のUIを扱っているため、CSR中心の構成になっています。
ただ、将来他のプロジェクトに参加したときにすぐ対応できるよう、今のうちに他のレンダリング方式も理解しておきたいという思いでこの記事を書きました。

初学者の頃に一度は学んだはずの概念ですが、意外と忘れていたことや、新たに発見したことも多く、良い復習になりました。
特に、「一見SSRでしか実現できなさそうなページでも、実はSSGにできるケースがある」と学べたのは、個人的に非常に印象的でした(これは海外のYouTuberから学びました)。

今後はNext.jsの概念的な部分を言語化してブログに落とし込んでいこうと思います。

参考:https://www.youtube.com/watch?v=S5tjBqzs31w&t=1083s

Discussion