SSRとPWAは両立できる?キャッシュ階層で理解する

SSRとPWAは「相性が悪い」のではなく、キャッシュの担当範囲が違います

「SSR(Server Side Rendering)とPWA(Progressive Web App)は両立できないのでは?」という疑問はよく出ます。
結論から言うと、両立は可能です。むしろ、役割を正しく分ければ互いの弱点を補完します。

問題が起きるのは、両者が同じことをしていると誤解している場合です。
SSRは描画の仕組み、PWAはオフライン機能…と説明されがちですが、本質はそこではありません。

SSRとPWAの違いは「描画場所」ではなく、キャッシュをどの階層で行うかです。

この2つは競合する技術ではなく、異なるレイヤーのキャッシュシステムです。

Webアプリは実は多層キャッシュで動いている

Webは単一のキャッシュで動いているわけではありません。少なくとも次の5段階のキャッシュが存在します。

場所 主な役割
ブラウザメモリ JSランタイム React state / SWRキャッシュ
Service Worker ブラウザ アプリケーションキャッシュ
HTTPキャッシュ ブラウザ 静的リソース再利用
CDNキャッシュ エッジサーバ 配信高速化
サーバキャッシュ アプリサーバ SSRレスポンス再利用

SSRとPWAが衝突するのは、この階層を意識せず同じレイヤーを奪い合ったときです。

SSRのキャッシュは「HTMLのキャッシュ」

SSRはサーバでHTMLを生成します。
つまりキャッシュ対象はページではなくレンダリング結果です。

例えばNext.jsでは、ISR(Incremental Static Regeneration)やページキャッシュが行われます。

これは次のようなイメージです。

  • ユーザがアクセス
  • サーバがHTMLを生成
  • CDNに保存
  • 次のユーザは生成せず配信

つまりSSRの最適化対象は「初回表示」です。
ここで重要なのは、SSRのキャッシュはブラウザではなくサーバ側に存在するという点です。

PWAのキャッシュは「リソースのキャッシュ」

一方PWAはService Workerを使い、ブラウザ側でキャッシュを行います。
キャッシュ対象はHTMLではありません。

  • JavaScript
  • CSS
  • 画像
  • APIレスポンス

つまりPWAは2回目以降の体験を最適化します。

ここで役割がはっきり分かれます。

  • SSR:最初の表示を速くする
  • PWA:次の操作を速くする

競合していません。

なぜ「両立しない」と言われるのか

原因はService Workerのfetchイベントです。

Service Workerは全HTTPリクエストを横取りできます。
ここで「HTMLまでキャッシュ」してしまうと問題が起きます。

self.addEventListener('fetch', event => {
  event.respondWith(caches.match(event.request))
})

この設定を行うと、SSRのHTMLが更新されても、ブラウザは古いHTMLを返し続けます。

結果として

  • 新しいSSRが反映されない
  • デプロイ後も古いUI
  • バグ修正が届かない

という事故が起きます。
SSRとPWAが衝突したように見えるのは、キャッシュ対象を誤ったことが原因です。

両立させる設計

SSRとPWAを両立させるには、キャッシュするものを分けます。

HTMLはキャッシュしない(SSRの領域)

JS/CSS/画像はキャッシュする(PWAの領域)

APIは条件付きキャッシュ

具体例です。

self.addEventListener('fetch', event => {
  const req = event.request

  if (req.mode === 'navigate') {
    // HTMLは常にネットワーク
    event.respondWith(fetch(req))
    return
  }

  // 静的リソースはキャッシュ優先
  event.respondWith(
    caches.match(req).then(res => res || fetch(req))
  )
})

これにより

  • 初回はSSR
  • 以降はPWA高速化

という構成になります。

SSR + PWAで実際に起きる挙動

正しく構成すると、ユーザ体験は次のようになります。

1回目アクセス
→ SSRにより高速表示(検索エンジンにも有利)

2回目アクセス
→ Service WorkerがJSをキャッシュして即起動

回線が不安定
→ 既存画面は操作可能

このとき重要なのは、PWAはSSRを置き換えません。
SSRの結果を再利用しているだけです。

注意すべきポイント

APIキャッシュの罠

APIレスポンスをPWAでキャッシュすると、次の問題が起きます。

  • 在庫情報が古い
  • コメントが反映されない
  • ユーザ状態がズレる

対策として、次のAPIはキャッシュしない方が安全です。

  • 認証情報
  • ユーザ固有データ
  • リアルタイムデータ

ログインとオフラインの衝突

ログイン後の画面をキャッシュすると、ログアウトしても表示できてしまう場合があります。
これは情報漏洩に近い挙動です。ログイン領域はNetwork Firstが無難です。

結局どちらを選ぶべきか

「SSRかPWAか」という選択はあまり意味がありません。
実際のWebアプリでは、どちらか一方だけで最適化するのは難しいです。

検索流入が重要ならSSRが必要です。
継続利用が重要ならPWAが効きます。

つまり2つは対立概念ではなく、時間軸の最適化です。

  • SSRは“最初の5秒”を最適化する技術
  • PWAは“使い続ける5分”を最適化する技術

キャッシュの階層を意識して設計すると、両者は矛盾せず共存します。
Webのパフォーマンスはフレームワークではなく、どこにキャッシュを置くかで決まります。
SSRとPWAを分けて考えるのではなく、同じアプリの異なるレイヤーとして設計することが重要です。