streaming-ssr について
React 開発者向けに、最新の React API を使うストリーミングサーバーサイドレンダリングを解説します。ページのシェルを先に送り、遅い部分を準備でき次第配信する方法を扱います。
主な機能
- 01HTML の段階的な配信
- 02シェルの早期ストリーミング
- 03Suspense 境界のサポート
- 04ネットワークのバックプレッシャー処理
- 05React ストリーミング API の説明
活用シーン
- 01大規模ページの初回描画遅延の軽減
- 02遅い部分より先に重要なページ内容を送信
- 03ネットワーク混雑時の応答性維持
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ patternsdev/ skills --skill streaming-ssrインストール後、Claude Code で/streaming-ssrを入力して読み込まれたか確認します。
詳細
ストリーミングによるサーバーサイドレンダリング
アプリケーションの内容を_ストリーミング_することで、アプリケーションをサーバーサイドレンダリングしながら Time To Interactive を短縮できます。現在のナビゲーションに必要なマークアップを含む大きな HTML 文字列をひとつ生成する代わりに、まずシェルを送信し、時間のかかる部分は後からストリーミングできます。クライアントが最初の HTML チャンクを受信した時点で、ページの解析と描画を開始できます。
最新の React のストリーミングでは、Node ランタイム上で renderToPipeableStream()、Web Stream ランタイム上で renderToReadableStream() を使用し、クライアントで hydrateRoot() を使ってレスポンスをハイドレートします。
使用する場面
- HTML を生成されるそばから段階的に送信して TTFB と FCP を改善したい場合に使用します
- 完全な HTML を待つと初回描画が遅れるような大規模なページで役立ちます
使用しない場面
- ホスティング環境がストリーミングレスポンスに対応していない場合(一部のサーバーレスプラットフォームではレスポンス全体がバッファーされます)
- HTML が小さく、ストリーミングによる意味のある改善が得られないシンプルな静的ページの場合
- スタック内のミドルウェアやリバースプロキシがレスポンスをバッファーし、ストリーミングの効果が損なわれる場合
手順
- 非推奨の
renderToNodeStreamではなく、renderToPipeableStream(React 18 以降)を使用します Suspense境界とストリーミングを組み合わせ、時間のかかる部分の読み込み中にコンテンツを段階的にストリーミングします- 重要なシェルの準備ができたらストリーミングを開始するため、
onShellReadyコールバックを使用します onErrorコールバックでストリーミング中のエラーを処理します
詳細
初期 HTML は、App コンポーネントからのデータチャンクとともにレスポンスオブジェクトに送信されます。
<!DOCTYPE html>
<html>
<head>
<title>Cat Facts</title>
<link rel="stylesheet" href="/style.css" />
<script type="module" defer src="/build/client.js"></script>
</head>
<body>
<h1>Stream Rendered Cat Facts!</h1>
<div id="approot"></div>
</body>
</html>
Node 上で最新の React ストリーミングを行うには、renderToPipeableStream を使用します。
import { renderToPipeableStream } from "react-dom/server";
app.use("*", (request, response) => {
let didError = false;
const { pipe } = renderToPipeableStream(<App />, {
bootstrapScripts: ["/build/client.js"],
onShellReady() {
response.statusCode = didError ? 500 : 200;
response.setHeader("Content-Type", "text/html");
pipe(response);
},
onError(error) {
didError = true;
console.error(error);
},
});
});
renderToString を使って App コンポーネントをサーバーサイドレンダリングする場合、レスポンスを送信するにはツリー全体のレンダリングが完了するまで待つ必要があります。ストリーミングでは、サーバーはシェルを早期にフラッシュし、時間のかかるコンテンツは準備ができた時点で引き続き送信できます。
概念
プログレッシブハイドレーションと同様に、ストリーミングも SSR のパフォーマンスを改善するために使えるレンダリング方式です。名前が示すとおり、ストリーミングでは、HTML のチャンクが生成されるそばから Node サーバーからクライアントへ送信されます。クライアントは、大規模なページであっても HTML の「バイト」を早く受信し始めるため、TTFB が短縮され、比較的一定に保たれます。主要なブラウザーはすべて、ストリーミングされたコンテンツや部分レスポンスの解析と描画を早期に開始します。レンダリングが段階的に進むため、FP と FCP が高速になります。
ストリーミングはネットワークのバックプレッシャーに適切に対応します。ネットワークが混雑してこれ以上バイトを転送できなくなると、レンダラーにシグナルが送られ、ネットワークの状態が改善するまでストリーミングが停止します。そのため、サーバーのメモリー使用量が減り、I/O の状況に対する応答性が向上します。これにより、Node.js サーバーは複数のリクエストを同時にレンダリングでき、負荷の大きいリクエストが軽いリクエストを長時間ブロックするのを防ぎます。その結果、厳しい条件下でもサイトの応答性が保たれます。
ストリーミング向けの React
React 18 では、最新のストリーミング API が導入されました。
- Node.js の HTTP レスポンス向けの
renderToPipeableStream(element, options)。 - エッジ環境などの Web Streams ランタイム向けの
renderToReadableStream(element, options)。
これらの API は、クライアントでの hydrateRoot() による Suspense 境界、onShellReady、onAllReady、プログレッシブハイドレーションに対応しています。
ストリーム出力は、シェルの準備ができるとすぐにバイトを出力できます。サーバーで時間のかかるチャンクのレンダリングを続けながら、レスポンスはデータのチャンクを段階的にクライアントへ送信します。
ストリーミング SSR - メリットとデメリット
ストリーミングは、React による SSR の速度向上を目指すもので、次のメリットがあります。
-
パフォーマンスの向上: サーバーでレンダリングが始まってすぐに最初のバイトがクライアントに届くため、TTFB は SSR よりも改善されます。また、ページサイズにかかわらず、より安定した結果が得られます。クライアントは HTML を受信するとすぐに解析を開始できるため、FP と FCP も短縮されます。
-
バックプレッシャーへの対応: ストリーミングはネットワークのバックプレッシャーや輻輳にうまく対応するため、厳しい条件下でも応答性の高いウェブサイトを実現できます。
-
SEO に対応: ストリーミングされたレスポンスは検索エンジンのクローラーが読み取れるため、ウェブサイトの SEO が可能になります。
ストリーミングの実装は、renderToString を renderToPipeableStream() に単純に置き換えるだけではない点に注意が必要です。SSR で動作するコードが、そのままストリーミングで動作しない場合があります。
-
サーバーレンダーパスを使ってマークアップを生成し、それを SSR のチャンクより前にドキュメントへ追加するフレームワーク。たとえば、先行する
<style>タグでページに追加する CSS を動的に決定するフレームワークがあります。 -
renderToStaticMarkupを使ってページテンプレートを生成し、動的コンテンツの生成にrenderToStringの呼び出しを埋め込んでいるコード。このようなケースではコンポーネントに対応する文字列が必要となるため、ストリームに置き換えることはできません。例:
res.write("<!DOCTYPE html>");
res.write(renderToStaticMarkup(
<html>
<head>
<title>My Page</title>
</head>
<body>
<div id="content">
{ renderToString(<MyPage/>) }
</div>
</body>
</html>);
ストリーミングとプログレッシブハイドレーションはどちらも、純粋な SSR と CSR の体験の隔たりを埋めるのに役立ちます。