SSR
Server-Side Rendering ・ えすえすあーる
リクエストごとにサーバでHTMLを組み立てて返す方式。表示は速いが、操作できるのはハイドレーション後。
✦ 30秒で試す — 航海画面は見えているのに、ボタンを押しても何も起きない数秒があるJSを3倍にすると、記事が画面に見えるまでの時間は?予想してみる概要
SSR(Server-Side Rendering)は、リクエストを受けるたびにサーバ側でHTMLを組み立てて返す方式です。ブラウザには最初から中身の詰まったHTMLが届くため、JavaScriptの実行を待たずにコンテンツが表示されます。PHPやRailsが動的にHTMLを返すのは古典的なWebの動作そのものですが、現代の文脈でSSRという言葉が使われるとき、それは主に「ReactやVueのコンポーネントをサーバでも実行してHTMLを生成する」ことを指します。
つまり現代のSSRは、SPAの開発体験(コンポーネントで宣言的にUIを書く)はそのままに、初期表示だけをサーバに肩代わりさせるハイブリッドです。Next.js(React)やNuxt(Vue)といったフレームワークがこの方式を主戦場にしています。
なぜ生まれたか
Webの歴史では、HTMLはずっとサーバで作るものでした。流れが変わったのは2010年代、SPAが主流になり、サーバは空のHTMLとJavaScriptの塊だけを返すようになってからです。開発体験と操作感は向上した一方で、2つの代償が顕在化しました。ひとつは初期表示の遅さ — JavaScriptのダウンロードと実行、さらにAPIの往復が終わるまで、ユーザーは白い画面を見つめることになります。もうひとつはSEOで、初期HTMLが空っぽのページは検索エンジンやSNSのプレビューにとって「中身のないページ」でした。
「サーバでHTMLを作れば解決するのは分かっている。しかしせっかくコンポーネントでUIを書いているのに、サーバ用のテンプレートを別に二重管理するのは避けたい」 — この要求に応えたのが、ブラウザ用のJavaScriptコードをサーバ(Node.js)でもそのまま実行してHTML文字列を出力するという発想です。React は最初期から renderToString でこれを可能にしており、2016年に登場した Next.js がルーティングやデータ取得まで含めて使いやすい形にまとめました。同じコンポーネントが初回はサーバで、以降はブラウザで動く。SSRはSPAを否定する回帰ではなく、SPAの弱点を埋める拡張として生まれました。
詳細
SSRの一往復とハイドレーション
現代のSSRのリクエスト処理を順に追うと、次のようになります。サーバはデータを取得してコンポーネントを実行し、できあがったHTMLを返します。ブラウザはそれをすぐ表示し、続けてJavaScriptのバンドルを取りに行きます。
鍵になるのが「ハイドレーション(hydration)」という工程です。サーバが返したHTMLは見た目こそ完成していますが、ボタンにはまだ何もつながっていない「乾いた」状態です。ブラウザは同じコンポーネントをもう一度実行して仮想の木を作り、それを手元のHTMLと突き合わせながら、イベントハンドラを結びつけていきます。乾いたHTMLに命を吹き込むのでハイドレーション(水分補給)と呼ばれます。
ここで大事なのは、表示と操作可能の間に時間差があることです。HTML が届いた瞬間に記事は読めますが、JS のダウンロードと実行、ハイドレーションが終わるまで、ボタンを押しても何も起きません。その間のクリックは取りこぼされます。JS バンドルが重いほど、そして端末の CPU が遅いほど、この「見えるのに効かない」時間は伸びます。下の部品で確かめてください。「読み込む」を押して記事が見えたらすぐ「いいね」を押すと、押した瞬間が帯に打たれます。
今日の星図: オリオン座
0
「読み込む」を押して、画面に記事が見えたらすぐ「いいね」を何度も押してください。押した瞬間が下の帯に打たれます。
ハイドレーションには特有の落とし穴もあります。サーバとブラウザで同じコンポーネントを実行するので、両者の出力が食い違うと突き合わせが失敗します。現在時刻や乱数、window の有無、ブラウザだけにある localStorage の値で表示を変えると、サーバの HTML とブラウザの結果がずれ、React はハイドレーション不一致の警告を出して該当部分をブラウザで描き直します。ちらつきの原因になるので、ブラウザでしか決まらない値はハイドレーションが終わってから反映するのが定石です。
ストリーミングSSR — できたところから流す
素朴なSSRには「全部そろうまで何も返せない」という弱点があります。ページの一部(おすすめ欄やコメント欄)が遅いAPIを待っていると、その待ち時間がページ全体の表示を止めてしまいます。React 18(2022年)はこれを、HTTP のレスポンスを少しずつ送るストリーミングで解きました。<Suspense> で囲んだ遅い部分は、まず代わりの表示(スケルトン)のまま殻を送り出し、データがそろったところで本物の HTML を同じレスポンスの続きとして流して差し替えます。
殻が早く届くので表示が早まり、JS の読み込みも早く始められます。ハイドレーションも部分ごとに進められ(選択的ハイドレーション)、読者が触ろうとした部分が優先されます。ただし JS が届く前のクリックは、ストリーミングでも取りこぼされます。
CSR・SSGとのタイミング比較
レンダリング方式の違いは「HTMLがいつ、どこで作られるか」で整理できます。
クライアントサイドレンダリング(CSR、純粋なSPA)は生成を全部ブラウザに任せるので初回が遅く、SSGはビルド時に作り置くので配信は最速ですが、リクエストごとに内容を変えられません。SSRはその中間で、「ユーザーごと・アクセスごとに違う内容を、それでも最初から完成したHTMLで返したい」場合の選択肢です。ログイン後のパーソナライズされたページや、在庫・価格が刻々と変わるECの商品ページが典型です。
代償: サーバの負荷と運用
SSRの代償は、リクエストのたびにサーバでコンポーネントの実行(=CPUを使う計算)が走ることです。静的ファイルを返すだけの構成と比べ、サーバの負荷は桁違いに大きく、トラフィックが増えればスケーリングやキャッシュ戦略が必要になります。最初の1バイトが届くまでの時間(TTFB)も、サーバ側のデータ取得を待つぶん静的配信より長くなります。ページ全体や断片を一定時間キャッシュする、CDNのエッジでレンダリング結果を再利用するなどの工夫が定番で、生成済みページを一定時間ごとに裏で作り直す ISR は SSG の側から歩み寄った同じ発想です。また「常時稼働するNode.jsサーバが必要」という運用上の重さから、サーバレスやエッジ環境でSSRを動かすデプロイ形態も一般化しました。
ハイドレーションを減らす工夫
部品で見たとおり、SSR の弱点は「HTML は速いのに、ページ全体分の JS を読み込んでハイドレーションし終えるまで操作できない」ことです。そこで近年の手法は、ハイドレーションする量そのものを減らす方向に進んでいます。
アイランドアーキテクチャ(Astro など)は、ページの大半を JS なしの静的な HTML として送り、ボタンやフォームのような対話する部分(島)だけを個別にハイドレーションします。この知星図も Astro で作られており、体験部品は画面に見えてから読み込まれる島です。React Server Components(RSC、React 19 で安定版)は、コンポーネントを「サーバでしか動かないもの」と「ブラウザでも動くもの('use client')」に分けます。サーバコンポーネントの JS はブラウザに送られないのでバンドルは小さくなりますが、クライアントコンポーネントは従来どおりハイドレーションされます。RSC はハイドレーションをなくす技術ではなく、ハイドレーションする範囲を絞る技術です。
ハイドレーションそのものをなくそうとしたのが Qwik のレジューマビリティ(resumability)です。サーバはイベントハンドラの場所と状態を HTML に書き込んでおき、ブラウザはコンポーネントを再実行せず、読者が実際にボタンを押した瞬間にそのハンドラの JS だけを読み込んで「続きから再開」します。
フレームワークと方式のグラデーション
実務でSSRをゼロから実装することは稀で、Next.js、Nuxt、SvelteKit、Astroといったフレームワークを使うのが普通です。これらの現代的フレームワークの特徴は、SSR・SSG・CSRを「サイト単位」ではなく「ページ単位・コンポーネント単位」で混ぜられることです。トップページはビルド時に生成し、商品ページはリクエスト時にレンダリングし、カートはブラウザだけで動かす、といった適材適所が設計の中心になります。「サーバとブラウザの分担をどこで切るか」という問いを巡って、手法は今も進化し続けています。
試問
模型のブラウザでニュース記事を読み込みます。表示の方式(CSR / SSR / SSG / ストリーミングSSR)と JS バンドルの大きさ(100〜1200KB)を選んで「読み込む」を押すと、記事が見えた時刻とボタンが効き始めた時刻が帯に残ります。
今日の星図: オリオン座
0
「読み込む」を押して、画面に記事が見えたらすぐ「いいね」を何度も押してください。押した瞬間が下の帯に打たれます。
