Next.jsのレンダリング方式比較|CSR・SSR・SSG・ISRの使い分け

Next.jsのレンダリング方式比較|CSR・SSR・SSG・ISRの使い分け

Iori Suzuki

Iori Suzuki

こんにちは、新卒エンジニアの鈴木です。

前回の記事では、Next.jsのApp Routerの基本的な仕組みとして、CSR / SSR / SSG / ISRという4つのレンダリング方式を紹介しました。

ただ、それぞれの特徴を理解しても、実際にどれを使えばいいのか疑問が残った方も多いのではないでしょうか?

今回は、各レンダリング方式のメリット・デメリットと具体的な使いどころを、実際のコード例を交えながら整理していきます。インフラにも触れるので、ぜひ最後まで読んでみてください。

※この記事はNext.js 16を前提に書いています。

CSR(Client Side Rendering)

仕組みと特徴

CSRは、ブラウザがサーバーから受け取った空のHTMLに対して、JavaScriptを実行することで画面を描画する方式です。

サーバー側では最小限のHTMLを返すだけで、実際の画面生成はすべてブラウザ側で行われます。データの取得もブラウザ側で行うため、ページの表示後に非同期でAPIを叩いてコンテンツを埋めていきます。

ユーザーの操作に応じてリアルタイムに画面を書き換えられるため、インタラクティブなUIとの相性が非常によい方式です。

なお、Reactで素の状態でアプリを作ると、この方式になります。

CSR(Client Side Rendering)仕組みと特徴

メリット

  • ユーザー操作に応じてリアルタイムに画面を更新できる
  • サーバーへの負荷がほとんどかからない
  • ページ遷移が速く、SPA的な滑らかな操作感を実現しやすい
  • 静的ホスティングだけで配信できるため、インフラがシンプルになる

デメリット

  • JavaScriptの実行が完了するまで画面が表示されないため、初期表示が遅くなりがち
  • クローラーに空のHTMLが届くため、SEOに弱い
  • ブラウザに送るJavaScriptの量が多くなり、パフォーマンスがクライアント環境に依存する
  • APIキーなどの機密情報をブラウザ側で扱うことになるため、セキュリティに注意が必要

コード例

Next.jsでCSRを実装するには、ファイルの先頭に'use client'を宣言します。これによりClient Componentsとして扱われ、useStateuseEffectが使えるようになります。

javascript

'use client'

import { useEffect, useState } from 'react'

type User = {
  id: number
  name: string
}

export default function UserDashboard() {
  const [users, setUsers] = useState<User[]>([])
  const [isLoading, setIsLoading] = useState(true)

  useEffect(() => {
	const fetchUsers = async () => {
		try {
			const res = await fetch('/api/users')
			const data = await res.json()
			setUsers(data)
		} finally {
			setIsLoading(false)
		}
	}
  	fetchUsers()
  }, [])

  if (isLoading) return <p>読み込み中...</p>

  return (
    <ul>
      {users.map((user) => (
        <li key={user.id}>{user.name}</li>
      ))}
    </ul>
  )
}

 
ポイントは、ページの初回表示時は空の状態でレンダリングされ、useEffectが走ってからデータが表示される点です。そのため、isLoadingのような読み込み状態の管理が必須になります。

向いているページとインフラ

社内の管理画面や、ログイン後にしか見えないダッシュボードがCSRの典型例です。

SEOが不要で、ユーザー操作が多く、データがユーザーごとに異なるページに向いています。反対に、検索流入を狙いたいページや初期表示速度を重視するページには不向きです。

CSRのインフラ構成※サーバーは不要で、S3 + CloudFrontやNetlifyなどの静的ホスティングで完結します。インフラがシンプルで、CDN経由のためアクセス増にもスケールしやすい。

CSRはNode.js実行環境を必要とせず、S3 + CloudFrontやNetlifyなどの静的ホスティングで配信できます。Node.js実行環境を管理する必要がないため、運用コストを大幅に抑えられるのも特徴です。

また、CDNを経由して配信できるため、アクセス数が増えてもスケールアウトの心配がほとんどありません。

SSR(Server Side Rendering)

仕組みと特徴

SSRは、リクエストのたびにNext.jsがHTMLを生成して返す方式です。

CSRの「初期表示が遅い」「SEOに弱い」という問題を、リクエスト処理時にデータ取得からHTML生成までを完結させることで解消しています。毎回HTMLを組み立て直すことになるので、常に最新のデータをページに反映できるのが大きな特徴です。

SSRの仕組みと特徴

メリット

  • 常に最新のデータをもとにHTMLを生成できる
  • クローラーに中身の入ったHTMLが届くため、SEOに強い
  • ユーザーごとに異なるコンテンツを返せる
  • APIキーなどの機密情報をサーバー側で完結させられるため、ブラウザに渡さずに済む

デメリット

  • リクエストのたびにHTMLを生成するため、サーバーの負荷が高くなりやすい
  • SSG / ISRと比べると初期表示速度が劣る
  • Node.js実行環境を常時稼働させる必要があり、インフラコストがかかる
  • キャッシュが効きにくく、アクセス集中時にレスポンスが遅くなる可能性がある

SSRはサーバーサイドでレンダリングを行うため、クライアント環境の影響を受けにくく、初期表示速度はCSRと比べて高速です。

一方で、画面遷移のたびにリクエストが発生するため遷移速度はCSRに劣る傾向があります。

コード例

App RouterでSSRを実装するには、Server Componentsのfetchcache: 'no-store'を指定します。

※Next.js 16ではno-storeがデフォルトになったため、オプションの指定自体が不要になりました。

これによりリクエストのたびにデータを取得し、キャッシュを使わずに最新のHTMLを生成します。

javascript

type User = {
  id: number
  name: string
}

export default async function UserPage() {
  const res = await fetch('https://api.example.com/users', {
  	// Next.js 16ではno-storeがデフォルトのため、オプション指定は不要
    cache: 'no-store', // キャッシュを使わず、毎回リクエスト時にデータを取得
  })
  const users: User[] = await res.json()

  return (
    <ul>
      {users.map((user) => (
        <li key={user.id}>{user.name}</li>
      ))}
    </ul>
  )
}

 
CSRと比べてコードがシンプルになっています。

useEffectuseStateが不要で非同期関数として直接データを取得できるため、サーバー側で処理が完結し、APIキーなどの機密情報をブラウザに渡さずに済むというメリットもあります。

向いているページとインフラ

ログインユーザーのマイページや、在庫数がリアルタイムで変わるECサイトの商品詳細ページなどがSSRの典型例です。「ユーザーごとに表示内容が異なる」かつ「SEOも必要」という条件が重なるページに特に向いています。

この方式ではリクエストのたびにHTMLを生成するため、常時稼働するNode.js実行環境が必要です。CSRやSSGのように静的ファイルを返すだけでは動かないため、S3などの静的ホスティングだけでは対応できません。

SSRのインフラ構成※常時稼働のサーバーが必要で、リクエストごとにサーバー処理が走るため、スケール設計が重要になります。
※Vercelはゼロ設定で対応していますが、AWSの場合はECS(常時稼働)か、コストを抑えられるLambda(コールドスタートに注意)が選択肢となります。

ではどこで実行環境を用意するかというと、一番手軽なのはVercelです。デプロイするだけでSSRが動き、スケーリングも自動で面倒を見てくれます。

AWSを使う場合はECSが一般的な選択肢になります。コンテナを常時稼働させる構成なので安定性が高く、トラフィックが多いアプリに向いています。

ただし、SSRを採用する際はインフラの設計と管理がセットでついてくるという点は、頭に入れておきましょう。

SSG(Static Site Generation)

仕組みと特徴

SSGは、ビルド時にすべてのページをHTMLとして生成しておく方式です。

CSRはブラウザ側でHTMLを組み立て、SSRはリクエストのたびにHTMLを生成しますが、SSGはあらかじめ完成したHTMLを用意しておき、リクエスト時はそのファイルをそのまま返すだけです。データへのアクセスはビルド時のみ発生するため、本番環境でのNode.js実行環境が不要になり、生成されたHTMLはCDNにキャッシュして高速に配信できます。

SSGの仕組みと特徴

メリット

  • あらかじめHTMLを生成しておくため、初期表示が非常に速い
  • CDNでキャッシュできるため、大量アクセスにも耐えられる
  • ビルド後はNode.js実行環境が不要なので、インフラ構成がシンプルでコストも抑えやすい
  • クローラーに中身の入ったHTMLが届くため、SEOに強い

デメリット

  • ビルド時点のデータしか持てないため、更新のたびに再ビルドが必要になる
  • ページ数が増えるとビルド時間が長くなる
  • ユーザーごとに異なるコンテンツを返せない
  • リアルタイム性が求められるページには向かない

コード例

SSGはビルド時にHTMLを全部作っておく方式なので、Next.jsはビルドの時点で「どのパスのページを作ればいいか」を把握している必要があります。

/aboutのような固定パスはURLが1つに決まっているため、Next.jsが自動で判断できます。一方、/posts/[slug]のような動的ルートは、どんなslugが存在するかがコードを見ただけではわからないため、generateStaticParamsで「このslugたちのページを作ってね」と明示的に教えてあげる必要があります。

javascript

import { fetchAllPosts, fetchPostBySlug } from '@/lib/api'
import { notFound } from 'next/navigation'

export const dynamicParams = false

type Props = {
  params: Promise<{ slug: string }>
}

// ビルド時に生成するパスを全列挙する
export async function generateStaticParams() {
  const posts = await fetchAllPosts()

  return posts.map((post) => ({
    slug: post.slug,
  }))
}

export default async function PostPage({ params }: Props) {
  const { slug } = await params
  const post = await fetchPostBySlug(slug)
  
  // generateStaticParams で列挙していないslugが来た場合は404
  if (!post) notFound()

  return (
    <article>
      <h1>{post.title}</h1>
      <p>{post.content}</p>
    </article>
  )
}

 
ポイントは、generateStaticParamsで返したslugのページしか生成されない点です。ビルド後に新しい記事が増えても、再ビルドしないとそのページは存在しません。「教えないと生成しない」という制約がSSGの性質です。

向いているページとインフラ

企業のコーポレートサイトや、ブログ記事のような「一度公開したらしばらく変わらない」ページがSSGの典型例です。更新頻度が低く、全ユーザーに同じ内容を表示するページに向いています。

SSGのインフラ構成※ビルド時に生成した静的ファイル(HTML/CSS/JS)をCDNにアップロードしておくことで、本番ではサーバー処理を介さずCDNがファイルをそのまま返します。
※APIへのアクセスはビルド時のみに限定されます。

SSRと違いビルド後はNode.js実行環境が不要で、S3 + CloudFrontやNetlifyなどの静的ホスティングで配信できます。サーバー負荷がなく、CDNでキャッシュできるため、コストを抑えつつ高速配信が可能です。CSRの構成と同じですね。

ISR(Incremental Static Regeneration)

仕組みと特徴

ISRは、SSGをベースにしつつ、一定時間ごとにページを再生成できる方式です。

SSGの弱点は「更新のたびにフルビルドが必要」という点でしたが、ISRはその問題を解消してくれます。

たとえば「60秒ごとに再生成する」と設定しておくと、60秒が経過したあとの最初のリクエストをトリガーにバックグラウンドで再生成が走ります。それまではキャッシュされたページが返されるため、ユーザーは待たされません。SSGの「速さ」とSSRの「データの新鮮さ」のバランスを取りたいときに有効な方式です。

ISRの仕組みと特徴

メリット

  • SSGと同様に初期表示が速く、CDNキャッシュが効く
  • 一定間隔でページを再生成できるため、SSGより新鮮なデータを表示できる
  • フルビルドが不要なため、ページ数が多くてもビルド時間を抑えられる
  • HTMLが生成された状態で配信されるため、SEOにも強い

デメリット

  • 再生成のタイミングによっては、古いデータが表示されることがある
  • ユーザーごとに異なるコンテンツを返せない
  • Vercel以外の環境でセルフホストする場合、再生成の仕組みを自前で構築する必要がある
  • リアルタイム性が求められるページには向かない

コード例

App RouterでISRを実装するには、fetchのオプションにnext: { revalidate: 秒数 }を指定します。この秒数が再生成の間隔となり、たとえば以下のように指定すると、60秒ごとに最新データでページが再生成されるようになります。

javascript

import { fetchPopularArticles } from '@/lib/api'
import { notFound } from 'next/navigation'

type Props = {
  params: Promise<{ slug: string }>
}

// デフォルトはtrueのため、明示しなくても同じ動作になります
// trueにしておくと、列挙していないパスへの初回アクセス時にその場でページを生成してくれます
export const dynamicParams = true

// 事前に生成しておくページを列挙する(任意)
export async function generateStaticParams() {
  // アクセスが多いページだけ事前に生成しておく
  const popularArticles = await fetchPopularArticles()
  return popularArticles.map((article) => ({ slug: article.slug }))
}

export default async function NewsPage({ params }: Props) {
  const { slug } = await params
  const res = await fetch(`https://api.example.com/articles/${slug}`, {
    next: { revalidate: 60 }, // 60秒ごとにページを再生成
  })

  if (!res.ok) notFound()

  const article = await res.json()

  return (
    <article>
      <h1>{article.title}</h1>
      <p>{article.content}</p>
    </article>
  )
}

 
SSGとの大きな違いは、dynamicParamsの挙動にあります。

SSGの場合、generateStaticParamsで列挙していないパスへのアクセスは404になりますが、ISRならその場でページを生成してキャッシュまでしてくれます。そのため、記事が増えるたびに再ビルドを行う必要がなくなります。

generateStaticParamsは「よく見られるページだけ事前にビルドして温めておく」といった用途で使うイメージですね。

向いているページとインフラ

ニュースサイトの記事一覧や、ECサイトの商品一覧ページがISRの典型例です。

「数分前の情報であれば許容できる」という場面に向いています。更新頻度はそれなりにあるものの、秒単位のリアルタイム性までは求められない、なおかつSEO対策も必要、といった条件が重なるページには、特に有効です。

ISRのインフラ構成※期限切れ後も古いキャッシュを即座に返すため、ユーザーを待たせずにバックグラウンドで再生成を行い、次のリクエストから新しいHTMLが返る仕組みです。Vercel以外はセルフホスト実装が必要になります。

Vercelを使うと、キャッシュの管理や再生成のトリガーをすべて担ってくれるため、設定なしでそのまま動き、開発者はコードに集中できます。

一方、AWS等でセルフホストする場合は、ISRの仕組みがVercelと密接に連携しているため、これらを自分で実装する必要があり、対応が複雑になりがちです。ISRを採用するのであれば、Vercelがよいと思います。

まとめ:実際には方式を組み合わせて使おう

前回の記事でも触れましたが、どのレンダリング方式が一方的に優れているというわけではなく、ページの用途に合わせて使い分けることが大切です。

判断に迷ったときは「SEOが必要か」「データがユーザーごとに異なるか」「更新頻度はどのくらいか」を確認することから始めれば、最適な方式を自然と絞り込めるはずです。

▼レンダリング方式の比較まとめ
※スマートフォンでご覧の方は、表を横にスクロールしてご確認いただけます。

CSR SSR SSG ISR
初期表示速度 遅い 中程度 速い 速い
データの更新タイミング リクエストのたびに更新 リクエストのたびに更新 ビルド時点 一定間隔で更新
ユーザーごとに異なるデータ × ×
SEO ×
サーバー負荷 低い 高い 低い 低い
ログイン・権限制御 × ×

アプリ全体で一つのレンダリング方式に統一するケースはほとんどなく、ページの性質に合わせて方式を使い分けるのが一般的です。

たとえばECサイトであれば、次のような組み合わせが考えられます。

  • トップページ・特集ページ:SSG(更新頻度が低く、SEOが重要)
  • 商品一覧ページ:ISR(在庫や価格はある程度新鮮に保ちたい)
  • 商品詳細ページ:SSR(在庫数をリアルタイムに反映したい)
  • マイページ・注文履歴:SSR(ユーザーごとに内容が異なり、認証も必要)
  • 管理画面:CSR(SEO不要で、操作が多い)

Next.jsではページ単位でレンダリング方式を選択できるため、このような柔軟な設計が可能です。「このページは何を優先するか」を考える習慣をつけると、自然と適切な方式を選べるようになります。

最後までご覧いただき、ありがとうございました。

この記事のシェア数

DX部に所属しWeb制作・開発業務を担当。学生時代はHTML/CSS/JavaScript/PHP/Java/pythonなどの基礎的なプログラミングスキルの習得に加え、データベースやクラウドサービスに関する技術も習得。Vue.jsやReact、Laravelを用いたWebアプリケーションの開発経験を積み卒業後LIGに新卒入社。

このメンバーの記事をもっと読む
10年以上の開発実績があるLIGが、最適な開発体制や見積もりをご提案します
相談する サービス詳細を見る