〇背景
CSR(Client-Side Rendering)という単語を知らずに詰められたので、Vue/Nuxt の文脈で “レンダリング方式の全体像” を整理する。
〇今回のコード例
今回のメモは概念中心。最後に Nuxt 3 を使った最小コード例をいくつか添付(SSR/SSG/ISR/Island)。
〇調べる前の自分の認識
- Vue は基本 CSR(SPA)で動く。
- Nuxt を使うと SSR/SSG ができる(はず…)。
- SEO は SSR/SSG が有利らしい。
→ 実際には CSR/SSR/SSG/ISR/Streaming/Islands/Edge など複数の方式があり、配信・UX・SEO・コスト・複雑性がトレードオフになる。
〇調べた結果(要点サマリ)
1) CSR(Client-Side Rendering)
- 初回は空の HTML + JS を返し、ブラウザで JS が実行されてから描画。
- 長所:CDN 配信が容易、ホスティングが安い、フロント主導で操作感が軽快。
- 短所:初回表示が遅くなりがち(FCP/TTI)、SEO が弱い(クローラは改善しているが完全でない)。
2) SSR(Server-Side Rendering)
- サーバで HTML を生成して返す → 初期描画が速い、SEO に強い。
- 返した HTML を クライアントでハイドレーション(hydration) してインタラクティブ化。
- コスト:サーバ実行基盤が必要(Node/Edge)。
3) SSG(Static Site Generation)
- ビルド時に 静的 HTML を生成して CDN から配信。
- 超高速・安価・スケール容易。更新頻度が低いページに最適。
- 動的データはクライアント取得 or エッジ関数と組み合わせる。
4) ISR(Incremental Static Regeneration / Incremental Static Revalidation)
- 静的生成 + 有効期限。期限切れ時にバックグラウンドで再生成して差し替え。
- 頻繁に変わるが “リアルタイムほどではない” ページに向く。
5) Streaming SSR(ストリーミング)
- サーバが HTML を分割送信。LCP を早めつつ、重い部分は後続で。
- 体感速度改善、長いページに有効。Nuxt3 は標準で対応。
6) Islands Architecture(部分ハイドレーション)
- ページはサーバ側で HTML 化し、必要なウィジェットだけをクライアントでハイドレート。
- JS 量を劇的に削減可能。Nuxt の / 遅延ハイドレーション系の仕組み・分割コンポーネントで実現。
7) Edge Rendering
- CDN のエッジ(近い場所)で SSR/ISR を実行(Nitro on Vercel/Netlify/Cloudflare)
- 低レイテンシ、グローバル配信に強い。
実運用では
Nuxt3(Nitro)
〇動作解説(図解)
CSR の流れ
[Browser] --GET--> [CDN/Static Host] --index.html(+bundle.js)--> [Browser]
[Browser] --runs JS--> fetch APIs --> render DOM
- 初回は白画面→JS 読込→API→描画。
SSR の流れ
[Browser] --GET--> [SSR Server/Edge]
└> fetch data -> render HTML (server)
<- HTML (fully rendered) --
[Browser] hydrate -> interactive
- 初回から内容が見える(SEO/シェアカードに強い)。
SSG の流れ
[Build time] fetch data -> generate HTML files
[Browser] --GET--> [CDN] <- prebuilt HTML/CSS/JS
- ページは事前生成。更新は再ビルド or ISR。
Islands(部分ハイドレーション)
<Page HTML (server-rendered)>
[static text]
<div id="island-1">widget A</div>
<div id="island-2">widget B</div>
// ブラウザでは island A/B の JS だけ読み込み・起動
- 大半は静的、必要箇所だけ JS。
〇実務での注意点(チェックリスト)
- ハイドレーション不一致:v-if と onMounted の条件差、Date.now()/Math.random() の使用で DOM が一致しない → SSR では 同一出力を保証。ランダムは mounted 後に。
- データ取得の二重実行:SSR + CSR で同じ API を二度叩く設計を避け、サーバで取得→状態注入(state serialization) を活用。
- 認証/セッション:SSR では サーバ側でクッキー/ヘッダを読む。機微情報はクライアントに埋め込まない。
- キャッシュ戦略:SSG/CDN キャッシュに Cache-Control, stale-while-revalidate。
- エッジ/リージョン:API と SSR 実行場所を近づける。遠いと TTFB が悪化。
- SEO:メタタグ/OGP は SSR/SSG で確実に出力。クライアント後書き換えはボツ。国際化は hreflang/meta。
- アナリティクス:SPA 遷移時に pageview 発火を忘れがち(router.afterEach)。
- エラーハンドリング:SSR 中の例外は 500 になりがち。Error Boundary / Nuxt の error ページを用意。
- パフォーマンス:
〇Nuxt 3 での最小コード例
A. SSR(デフォルト)
pages/index.vue
<script setup lang="ts">
const { data } = await useFetch('/api/hello')
</script>
<template>
<h1>SSR Page</h1>
<p>{{ data?.message }}</p>
</template>
server/api/hello.get.ts
export default defineEventHandler(() => ({ message: 'Hello from server' }))
B. SSG(プリレンダー)
nuxt.config.ts
export default defineNuxtConfig({
nitro: { prerender: { routes: ['/', '/about'] } }
})
→ npx nuxi build で静的出力。CDN へ配信。
C. ISR 的リバリデーション
server/routes/posts.ts(例)でヘッダ付与 or キャッシュキー制御。
export default defineEventHandler(async (event) => {
// データ取得...
setHeader(event, 'Cache-Control', 'public, max-age=60, stale-while-revalidate=600')
return await getPosts()
})
⇒ 60 秒以内は即返、期限切れ後にバックグラウンド更新(CDN と合わせて運用)。
D. Islands(部分ハイドレーション)
pages/index.vue
<template>
<section>
<StaticHero />
<ClientOnly>
<InteractiveChart />
</ClientOnly>
</section>
</template>
→ StaticHero は SSR のみ、InteractiveChart だけクライアントでハイドレート。
〇方式選定の指針(早見表)
| 目的 | 推奨 | 理由 |
|---|---|---|
| ブログ/ドキュメント | SSG (+ ISR) | 高速/安価、変更は定期リビルド or 再検証で十分 |
| EC 商品一覧 | SSR + キャッシュ | SEO・初期描画◎、在庫/価格をサーバ側で統一 |
| ダッシュボードSPA | CSR/Islands | 認証必須・インタラクション重視、JS を必要箇所に限定 |
| グローバル配信 | Edge SSR/ISR | レイテンシ低、地域別 A/B も実装容易 |
〇まとめ・所感
- 単一の正解はない。 ページの性質(更新頻度・SEO 重要度・双方向性)と 運用コスト を秤にかける。
- Vue 単体なら CSR/Islands を軸に、Nuxt3 を使えば SSR/SSG/Streaming/Edge/ISR まで柔軟に選べる。
- 次回以降は ハイドレーション不一致を再現→解決、Nuxt Image での最適化 など手を動かして検証する。
レンダリング方式も技術選定の際には必要そうだな。