Server-Side Rendering (SSR) - 二相実行モデルとHydrationの制約

ロードマップ: Vue.js学習ロードマップ

What(何についてか)

Server-Side Rendering(SSR)は、Vueコンポーネントをブラウザではなくサーバー側でHTML文字列として描画し、その完成済みHTMLをクライアントへ返す仕組みである。クライアントは受け取った静的HTMLをすぐ表示でき、その後 Vue が hydration を行うことで、同じDOMにイベントやリアクティブ更新を結び付けて対話可能なアプリへ移行する。

SSRでは同じアプリケーションコードがサーバーとクライアントの両方で動作するため、Vueアプリは “isomorphic” または “universal” な実行モデルを持つ。

Why(なぜ必要か)

SSRの主な利点は次の通りである。

  • 初回表示速度(time-to-content)の改善
  • SEOの改善
  • サーバーサイドとクライアントサイドを同じVueコンポーネントモデルで扱えることによる思考統一

特に初回表示速度は重要で、ブラウザがJavaScriptのダウンロードと実行を完了する前に、サーバーが返した完成済みHTMLを表示できる。これは slow network / slow device 環境で効果が大きい。

一方で、SSRには明確なコストもある。

  • browser専用APIへの制約
  • ビルドとデプロイの複雑化
  • Node.js実行環境の必要性
  • サーバーCPU負荷の増加

そのため、社内ダッシュボードのようにSEOや初回表示速度がそこまで重要でないアプリに対しては過剰設計になりやすい。

How(どう動くか)

SSRの基本フロー

flowchart LR
  A["Vue components"] --> B["Server renders HTML"]
  B --> C["Browser shows static HTML"]
  C --> D["Client hydration"]
  D --> E["Interactive Vue app"]

最小構成

SSRでは createSSRApp()renderToString() が基本APIになる。

import { createSSRApp } from 'vue'
import { renderToString } from 'vue/server-renderer'
 
const app = createSSRApp({
  data: () => ({ count: 1 }),
  template: `<button @click="count++">{{ count }}</button>`
})
 
renderToString(app).then((html) => {
  console.log(html)
})

この段階で得られるのは静的HTMLであり、クライアントでまだVueは動いていないため、画面は表示されてもボタン操作などは動作しない。

Hydration

クライアント側では同じアプリを createSSRApp() で再生成し、既に存在するSSR済みDOMへ接続する。

import { createSSRApp } from 'vue'
 
const app = createSSRApp({
  // same app as server
})
 
app.mount('#app')

この mount は新規DOM生成ではなく、既存DOMを引き継いでイベントやリアクティブ更新を接続する hydration として動作する。

Universal code

SSRでは同じアプリ生成ロジックを server / client の両方で再利用するため、共通の createApp() を切り出す構造が基本となる。

// app.js
import { createSSRApp } from 'vue'
 
export function createApp() {
  return createSSRApp({
    data: () => ({ count: 1 }),
    template: `<button @click="count++">{{ count }}</button>`
  })
}

server と client がこの共通ロジックを使うことで、同じアプリ構造を二度実行できる。

SSR vs. SSG

SSRはリクエストごとにサーバーがHTMLを生成する。一方、Static Site Generation(SSG)はビルド時に一度だけHTMLを生成し、以後は静的ファイルとして配信する。

観点SSRSSG
HTML生成タイミングリクエストごとビルド時
ユーザーごとの差分扱いやすい扱いにくい
配信コスト高い低い
運用複雑性高い低い
向く用途動的ページ、初回表示重視docs、blog、marketing pages

静的コンテンツ中心なら、SSRよりSSGのほうが合理的な場合が多い。

SSR特有の制約

Browser-specific code

SSRの最初の実行はNode.js上で行われるため、windowdocumentlocalStorage などの browser 専用APIはそのまま使えない。DOM依存の処理は mounted / onMounted などの client-only lifecycle に寄せる必要がある。

Lifecycle の違い

サーバー実行時には DOM を前提とする lifecycle hook は動作しない。したがって、初期データ構築とDOM副作用を分離して設計する必要がある。

Cross-request state pollution

SSRでは server process が複数リクエストをさばくため、module scope の singleton state を共有すると、あるユーザーの状態が別のユーザーに混入する危険がある。

そのため、SSRでは app、router、store を リクエストごとに新規生成 するのが原則である。

flowchart TD
  A["Request A"] --> S["singleton store"]
  B["Request B"] --> S
  S --> R["state contamination risk"]

Piniaのような state management ライブラリは、この問題を前提にSSR対応設計を持つ。

Hydration mismatch

サーバーが生成したHTMLと、クライアントが期待するDOM構造が一致しないと hydration mismatch が起きる。主な原因は次の通りである。

  • invalid HTML nesting
  • ランダム値の利用
  • server / client 間の timezone 差
  • server / client で異なるデータを使うこと

Vueは可能な範囲で自動復旧を試みるが、パフォーマンス低下や不安定化を招くため、開発中に原因を除去するのが基本である。

高レベルソリューション

理論上は renderToString() を使って生のSSRアプリを構築できるが、本番では次のような問題を自力で扱う必要がある。

  • client build と server build の二系統管理
  • SFCコンパイル
  • asset link や resource hint の埋め込み
  • routing、data fetching、state management の universal 対応
  • SSR と SSG の切り替えや混在

そのため、実務ではNuxtのような高レベルソリューションを使うのが合理的である。SSRは原理理解と、フレームワークの制約がなぜ存在するかを理解するために学ぶ価値が高い。

Options API → Composition API 差分(補足)

項目Options API(旧)Composition API(新)
アプリ生成createSSRApp は同じだが、内部ロジックは data / methods 中心setup / composable 中心で universal code を組みやすい
browser API 分離mounted() に寄せるonMounted() に寄せる
shared logicmixins で共有しがちcomposable で server/client 共通ロジックを切り出しやすい
state 注入Options寄り構文で provide/injectComposition API で provide/inject や composable を明示的に扱いやすい

Key Concepts

用語説明
SSRサーバーでVueをHTML化して返す方式
hydration既存のSSR済みDOMへVueを接続して対話可能にする工程
universal codeserver/client の両方で共有されるアプリコード
time-to-contentユーザーが最初に内容を見られるまでの速さ
SSGビルド時に静的HTMLを生成して配信する方式
cross-request state pollutionリクエスト間で状態が混入するSSR特有の問題
hydration mismatchserver と client のDOM期待が不一致になる問題
createSSRAppSSR用Vueアプリ生成API
renderToStringVueアプリをHTML文字列へ描画するSSR API

実務での判断軸

  • SEOや初回表示速度が極めて重要ならSSRを検討する
  • 静的コンテンツ中心ならSSRよりSSGを優先する
  • 社内ダッシュボードのような閉じた業務SPAではSSRは過剰になりやすい
  • SSRを使うなら server/client 二相実行、singleton state 問題、hydration mismatch を前提に設計する
  • 実務では生SSRを手組みするより、高レベルフレームワークを使うのが合理的である