Web Streams APIの概要と仕組み

Web Streams APIは、**ストリーム**（逐次的なデータの塊）を扱うための標準的なインターフェイスです。大容量のデータを一度に全て読み込まず、**データをチャンク（小塊）に分割して順次処理**することで効率的に扱えます ([Using web streams on Node.js](https://2ality.com/2022/06/web-streams-nodejs.html#:~:text=,process%20one%20at%20a%20time))。Webブラウザで策定された仕様ですが、現在では**ブラウザ、Node.js、Denoなど主要なJavaScript環境**でサポートされています ([Node.js Streams vs. Web Streams API | Better Stack Community](https://betterstack.com/community/guides/scaling-nodejs/nodejs-streams-vs-web-streams-api/#:~:text=In%20recent%20years%2C%20the%20WHATWG,js%2C%20and%20Deno))。  

**● 基本概念（ReadableStream・WritableStream・TransformStream）**:  
Web Streams APIには3種類の主要なストリーム型があります ([Using web streams on Node.js](https://2ality.com/2022/06/web-streams-nodejs.html#:~:text=There%20are%20three%20main%20kinds,of%20web%20streams)):

- **ReadableStream** – データのソースを表す「読み取り可能な」ストリームです。たとえばネットワークから届くデータやファイルの内容などをチャンク単位で読み取ることができます（ストリームの利用側のコードを**コンシューマ**と呼びます） ([Using web streams on Node.js](https://2ality.com/2022/06/web-streams-nodejs.html#:~:text=,that%20is%20called%20a%20consumer))。  
- **WritableStream** – データの送信先を表す「書き込み可能な」ストリームです。ここにデータを書き込むことで、ファイルへの保存やネットワーク送信などを逐次的に行えます（ストリームへデータを書き込むコードを**プロデューサ**と呼びます） ([Using web streams on Node.js](https://2ality.com/2022/06/web-streams-nodejs.html#:~:text=,that%20is%20called%20a%20consumer))。  
- **TransformStream** – 入力用のWritableStreamと出力用のReadableStreamのペアから構成されるストリームです。**データの変換処理**を行う中間ストリームとして機能し、ReadableStreamの`pipeThrough()`メソッドでパイプラインに挿入できます ([TransformStream - Web APIs | MDN](https://developer.mozilla.org/en-US/docs/Web/API/TransformStream#:~:text=It%20may%20be%20passed%20to,stream%20from%20XML%20to%20JSON))。例えば、**動画フレームのデコード/エンコード、データ圧縮/展開、XMLからJSONへの変換**といった処理を実装できます ([TransformStream - Web APIs | MDN](https://developer.mozilla.org/en-US/docs/Web/API/TransformStream#:~:text=It%20may%20be%20passed%20to,stream%20from%20XML%20to%20JSON))。Web標準でいくつかのTransformStream実装が提供されており、例えば**`TextDecoderStream`**（UTF-8バイト列を文字列に変換）や**`CompressionStream`**（バイナリデータをGZIP圧縮）などがあります ([Using web streams on Node.js](https://2ality.com/2022/06/web-streams-nodejs.html#:~:text=,GZIP%20and%20other%20compression%20formats))。  

**● ストリームのパイプライン処理**:  
Streams APIでは、ReadableStreamからWritableStreamへ**パイプ（接続）**してデータを逐次流すことができます。たとえば、`readableStream.pipeTo(writableStream)`を呼ぶと、ReadableStreamから読み取ったデータを直接WritableStreamへ送り込むことが可能です ([ReadableStream - Web APIs | MDN](https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream#:~:text=ReadableStream))。また、`readableStream.pipeThrough(transformStream)`を使うと途中にTransformStreamを挟んでデータ変換しつつパイプできます ([ReadableStream - Web APIs | MDN](https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream#:~:text=ReadableStream))。これらのメソッドは**バックプレッシャー**も管理しており、送り先のWritable側が一杯の場合はReadable側の読み出し速度を自動的に調整してくれます。  

**● Streams APIの利用例**:  
- *Fetch APIでの活用*: Fetch APIはネットワークレスポンスを表す`Response`オブジェクトの`body`プロパティにReadableStreamを提供しています ([ReadableStream - Web APIs | MDN](https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream#:~:text=The%20,of%20a%20%2054%20object))。つまり、`fetch()`した結果を**ストリームとして読み込む**ことが可能です。例えばHTTPレスポンスが巨大なJSONや動画ファイルでも、全体をメモリに置かずチャンクごとに処理できます。以下はFetch APIのレスポンスボディを逐次読み取るコード例です（テキストデータを扱うため`TextDecoderStream`でUTF-8デコードしています）:

```js
// サーバーからテキストデータをストリーミング取得する例
const response = await fetch('https://example.com/large-text');
const textStream = response.body.pipeThrough(new TextDecoderStream());  // バイト→文字列に変換
for await (const chunk of textStream) {
    console.log('受信チャンク文字列:', chunk);
    // 逐次チャンクデータを処理（表示や解析など）
}
console.log('ストリームの読み取り完了');
```  

上記のように`for await...of`ループを用いると、FetchレスポンスのReadableStreamから**非同期イテレーション**で簡潔にチャンクを取得できます ([ReadableStream - Web APIs | MDN](https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream#:~:text=const%20response%20%3D%20await%20fetch%28,let%20total%20%3D%200))。Fetchが返すReadableStreamは`Uint8Array`（バイト列）として各チャンクを提供しますが、`TextDecoderStream`を挟むことで文字列に変換しつつ処理しています。  

- *Node.jsでの活用*: Node.jsでは従来から`stream`モジュールによるストリームAPIが存在しましたが、現在は**Web Streams APIにも対応**しつつあります ([Node.js Streams vs. Web Streams API | Better Stack Community](https://betterstack.com/community/guides/scaling-nodejs/nodejs-streams-vs-web-streams-api/#:~:text=In%20recent%20years%2C%20the%20WHATWG,js%2C%20and%20Deno))。たとえばNode.js v18以降では実験的に`fetch`や`ReadableStream`がグローバルで利用でき、レスポンスの`body`はWeb StreamsのReadableStreamとして取得できます。Node.js環境でWeb Streamsを扱う際には、必要に応じて**Node.js独自のストリームと相互変換**も可能です。例として、Nodeの組み込みユーティリティ`stream`には`Readable.fromWeb()`というメソッドがあり、Web StreamsのReadableStreamをNode.jsのReadableストリームに変換できます ([Readable Streams in Node.js](https://blog.dennisokeeffe.com/blog/2024-07-08-readable-streams-in-nodejs#:~:text=Readable%20Streams%20in%20Node,then%20pipe%20this%20stream))。これにより**サーバサイドでもWeb Streams APIを使ったパイプライン処理**（例えばファイル->ネットワーク応答へのストリーミング送信など）が行えます。Node.jsとWeb Streams APIの具体的な違いや使い分けについては、環境固有の最適化（ファイルシステムアクセスはNode Streams、Web標準処理はWeb Streamsなど）を考慮して選択するとよいでしょう。

# React Concurrent Streaming (RCS) の概要と仕組み

React 18以降では、**Concurrent Rendering（コンカレントレンダリング）**と呼ばれる新しいレンダリング方式や、これを活かした**ストリーミングSSR**（サーバサイドレンダリング）の強化が導入されています。RCSとは、これら**Reactの並行レンダリング機能を利用したストリーミング描画**全般を指す概念で、特に**サーバコンポーネント**(React Server Components)を含めた最新のSSR手法を含んでいます。

**● ReactのConcurrent Renderingの基礎**:  
従来のReact（同期レンダリング）では、状態更新やレンダリング処理がブロッキング的に実行され、重い処理があるとUI更新が滞ることがありました。React 18のConcurrent Modeでは、内部的にレンダリングを**分割し優先度付け**する仕組みが導入され、処理を中断・再開したり、急ぎの更新を先に行ったりといった**柔軟なスケジューリング**が可能になっています。これによりブラウザのメインスレッドを占有する時間が短縮され、ユーザ入力への応答性が向上します。  

SSR（サーバサイドレンダリング）の場合、Concurrent Renderingの概念は主に**ストリーミング出力**の文脈で活用されます。Reactはレンダリング結果（HTML文字列など）を逐次生成し、準備ができた部分から順次クライアントに送信できます。これを支えるのがReact 18の**非同期レンダリングとSuspenseの組み合わせ**です。  

**● Streaming SSR（ストリーミングSSR）の仕組み**:  
React 18以前にもSSRでストリーム出力する`renderToNodeStream()`が存在しましたが、Suspenseと連携して**データの到着を待つ**ような動作はできず、結果的にすべてのHTMLを生成し終わるまでクライアントに送れないケースがありました ([A guide to streaming SSR with React 18 - LogRocket Blog](https://blog.logrocket.com/streaming-ssr-with-react-18/#:~:text=The%20most%20significant%20change%20compared,not%20suffer%20from%20these%20limitations))。React 18ではSSRが大幅に刷新され、**Suspenseを用いた遅延コンテンツの部分出力**が可能になりました。  

具体的には、React要素ツリー中に`<Suspense fallback={<Loading/>}>`を配置すると、サーバ側レンダリング時にそのboundary（境界）内部のコンテンツが**未解決（データ待ち）の場合でも、外側のHTMLを先にクライアントへ送信**できます。React 18の新しいサーバレンダリング関数は、まず**「シェル」（Shell）**と呼ばれる**即座にレンダリング可能な部分**を出力し、残りの非同期部分は準備でき次第、後からストリームに流し込みます ([A guide to streaming SSR with React 18 - LogRocket Blog](https://blog.logrocket.com/streaming-ssr-with-react-18/#:~:text=When%20the%20content%20above%20the,piping%20it%20into%20the%20response)) ([A guide to streaming SSR with React 18 - LogRocket Blog](https://blog.logrocket.com/streaming-ssr-with-react-18/#:~:text=This%20rendering%20function%20fully%20integrates,incrementally%20based%20on%20data%20requirements))。  

例えば以下のようなReactコンポーネントを考えます:

```jsx
<App>
  <Header />                {/* すぐレンダリング可能 */}
  <Suspense fallback={<Spinner/>}>
    <Article />            {/* データ取得が必要 */}
  </Suspense>
  <Footer />                {/* すぐレンダリング可能 */}
</App>
```

React 18のSSRでは、`<Header>`と`<Footer>`はすぐにレンダリングされ、`<Spinner>`（フォールバックUI）も含めた**初期HTML**がまずクライアントに送られます。一方、`<Article>`内で例えばデータフェッチが行われている場合、その完了を待ってから本来の内容が後続のHTMLストリームとして送信され、クライアント側でそれまで表示していた`<Spinner>`と差し替わります。こうした**逐次的・非ブロッキングなHTML送信**により、SSRでありながらクライアントはできる限り早く部分的にレンダリング結果を受け取れるようになります ([A guide to streaming SSR with React 18 - LogRocket Blog](https://blog.logrocket.com/streaming-ssr-with-react-18/#:~:text=This%20rendering%20function%20fully%20integrates,incrementally%20based%20on%20data%20requirements))。  

この仕組みを実現するために、React 18では**サーバ側API**として`ReactDOMServer.renderToPipeableStream()`および`renderToReadableStream()`が導入されました（後述）。これらは**ConcurrentなストリーミングSSR**を行うための関数で、Suspenseやコード分割（`React.lazy`）と深く統合されています ([A guide to streaming SSR with React 18 - LogRocket Blog](https://blog.logrocket.com/streaming-ssr-with-react-18/#:~:text=This%20rendering%20function%20fully%20integrates,incrementally%20based%20on%20data%20requirements))。その結果、**HTMLおよびデータフェッチのウォーターフォール問題（逐次待ちによる遅延）を解消**し、データ要件に応じてアプリケーションを**インクリメンタル（段階的）にレンダリング・送信**できるようになっています ([A guide to streaming SSR with React 18 - LogRocket Blog](https://blog.logrocket.com/streaming-ssr-with-react-18/#:~:text=This%20rendering%20function%20fully%20integrates,incrementally%20based%20on%20data%20requirements))。

**● React 18以降の新機能（Suspense・Server Componentsなど）**:  
React 18では上記のSuspenseによるストリーミングSSRに加え、将来的なフル機能のReact Server Components (RSC) の土台となる機能も導入されています。**React Server Components**とは、一部のコンポーネントをサーバ上でレンダリングし、**その結果（UIの記述データ）をクライアントへストリーム送信して統合**する仕組みです。RSCはReact 18ではまだ実験的機能ですが、Reactチームが提案する「ゼロバンドルサイズ」でのサーバ側UIロジック実行を可能にします。RSC用のプロトコルは**“React Flight”**と呼ばれ、テキストベースのストリームでコンポーネントツリーを表現します。クライアント側ではそれをパースして通常のReactコンポーネントに戻す必要があります ([React Server Components, without a framework?
    
    
    ](https://timtech.blog/posts/react-server-components-rsc-no-framework/#:~:text=%23%23%20%60createFromReadableStream%60%20from%20%60react))。  

React公式のRSCデモ実装では、`react-server-dom-webpack`パッケージを利用しており、クライアントには`createFromReadableStream()`という関数が提供されています。これは**サーバから送られてきたReadableStreamを受け取り、ReactのFlightプロトコルメッセージをパースして「サーバコンポーネントのツリー」を再構築する**ラッパー関数です ([React Server Components, without a framework?
    
    
    ](https://timtech.blog/posts/react-server-components-rsc-no-framework/#:~:text=%23%23%20%60createFromReadableStream%60%20from%20%60react))。言い換えれば、`createFromReadableStream`はReactのFlightクライアントAPIのラッパーであり、サーバコンポーネントの受信処理を簡素化します ([React Server Components, without a framework?
    
    
    ](https://timtech.blog/posts/react-server-components-rsc-no-framework/#:~:text=%23%23%20%60createFromReadableStream%60%20from%20%60react))。React 18ではこの他にも、（実験段階のものも含め）`React.use()`フックによるサーバからの値読み取りや、`SuspenseList`コンポーネント、`useId`による一意ID生成などSSRを支える様々な新機能が追加されています。  

まとめると、React Concurrent Streaming (RCS)とは**Reactのコンカレント機能（非同期レンダリング・Suspense等）を駆使してSSRをストリーミング実行する一連の技術**を指し、必要に応じてServer Componentsのストリーミングも含めた最先端のSSR手法です。次節では、このRCSを実現する上で**Web Streams APIが果たす役割**について詳しく見ていきます。

# Web Streams APIとReact Concurrent Streamingの関係性

ReactのストリーミングSSRは、本質的には「サーバで生成されるHTML出力を**逐次クライアントに送り出す**」ことです。この逐次送信を可能にするのが**ストリーム**であり、React 18では実行環境に応じて**Node.jsのストリーム**または**Web Streams APIのReadableStream**を利用します。すなわち、**Web Streams APIはReactのストリーミングSSRの裏側でHTMLやコンポーネントデータを転送するための基盤**として機能します。  

**● Web Streams APIを用いたストリーミングSSRの実装方法**:  
React 18のSSR関数には、Node.js向けの`renderToPipeableStream`と、ウェブ環境向けの`renderToReadableStream`が用意されています。どちらもReact要素ツリーをストリームにレンダリングするAPIですが、返却するストリームの型が異なります。Node.js（サーバランタイム）では**Node.jsのReadableストリーム**が返され、Web Streams対応ランタイム（DenoやCloudflare Workersなど）では**Web Streams APIのReadableStream**が返されます ([renderToPipeableStream – React](https://react.dev/reference/react-dom/server/renderToPipeableStream#:~:text=This%20API%20is%20specific%20to,runtimes%2C%20should%20use%20renderToReadableStream%20instead))。React公式ドキュメントでも「このAPIはNode.js専用。DenoやエッジランタイムのようにWeb Streamsが使える環境では`renderToReadableStream`を使うべき」と明記されています ([renderToPipeableStream – React](https://react.dev/reference/react-dom/server/renderToPipeableStream#:~:text=This%20API%20is%20specific%20to,runtimes%2C%20should%20use%20renderToReadableStream%20instead))。

実装の観点では、**Node.jsサーバでのSSR**では`renderToPipeableStream()`を呼び出して得られたストリームをHTTPレスポンスにパイプし、クライアントへ送信します。一方、**Deno DeployやCloudflare Workers**のような環境では、`renderToReadableStream()`で得たReadableStreamをそのまま`Response`オブジェクトに渡して返す、という形になります。どちらの場合もReactはバックグラウンドで適切にSuspense境界を判断し、**準備のできたHTMLチャンクを順次ストリームに流す**ため、開発者はストリームをパイプ接続するだけでストリーミングSSRを実現できます。  

**● `renderToPipeableStream` と `renderToReadableStream` の違い**:  
前述のとおり、両者の違いは主に**利用環境**と**返り値の型**にあります。`renderToPipeableStream`は**Node.js専用**であり、戻り値として`stream.Readable`（Nodeのストリーム）を提供します ([A guide to streaming SSR with React 18 - LogRocket Blog](https://blog.logrocket.com/streaming-ssr-with-react-18/#:~:text=The%20most%20significant%20change%20compared,not%20suffer%20from%20these%20limitations))。一方、`renderToReadableStream`はブラウザの標準`ReadableStream`を返す関数で、**DenoやEdge Runtime用**に設計されています ([What's New in React 18 - Medium](https://medium.com/@Brilworks/whats-new-in-react-18-31e70ae4680d#:~:text=What%27s%20New%20in%20React%2018,as%20Deno%20and%20Cloudflare%20workers))。機能的にはどちらもSuspense対応のストリーミングSSRを実現しますが、例えば**Node.jsのHTTPレスポンスオブジェクト**（`http.ServerResponse`）にはNodeストリームをそのまま`pipe()`できるためNode版APIを使い、**Service WorkerのFetchイベント**では`Response`を直接返す必要があるためWeb Streams版APIを使う、という形で使い分けます。  

内部実装では、この2つのAPIはReactドメインの共通ロジックを共有しており、出力先インターフェースだけを切り替えていると考えてよいでしょう。したがって**ストリーミングされる内容（HTML構造や順序）は同一**ですが、Node版は`.pipe(res)`でHTTP送信し、Web Streams版は例えば`return new Response(stream)`のように使われます。React 18の発表ブログでも、「モダンなエッジ環境向けに`renderToReadableStream`を追加した」と紹介されています ([What's New in React 18 - Medium](https://medium.com/@Brilworks/whats-new-in-react-18-31e70ae4680d#:~:text=What%27s%20New%20in%20React%2018,as%20Deno%20and%20Cloudflare%20workers))。

**● フロントエンドでのデータ受信・処理**:  
ストリーミングSSRで送られてきたデータをフロントエンドでどのように処理するかは、**送られてくる内容**によって異なります。

- **通常のHTMLストリーミングの場合**: サーバから送信されるのはプレーンなHTML文書です。ブラウザはHTTPチャンクを受け取るごとにHTMLを順次パース・レンダリングするため、特別な受信処理をしなくても**ユーザはコンテンツが徐々に表示される**のを確認できます。開発者側では、通常SSRと同様に`ReactDOM.hydrateRoot()`（React 18以降は`hydrateRoot`）を使って既存のサーバレンダリング済みDOMにイベントを紐付け、Reactによるインタラクティブな状態にします。なお、React 18の`hydrateRoot`は**部分的なHydration（選択的ハイドレーション）**にも対応しており、ストリーミング中でも利用可能です ([A guide to streaming SSR with React 18 - LogRocket Blog](https://blog.logrocket.com/streaming-ssr-with-react-18/#:~:text=%2F%2F%20client%2Findex,from%20%27.%2FApp))。つまり、サーバから送られてきたHTMLの一部だけがまだプレースホルダ（例えば`<div>Loading...</div>`のようなSuspenseフォールバック）の状態でも、クライアントはその部分を飛ばして先に他の要素をHydrationし、後から差し込まれた本来のコンテンツを検知して自動的にHydrationを完了する仕組みになっています。これにより、ストリーミングSSRでもクライアント上での初期化が順調に進み、インタラクション可能な部分から先に有効化されていきます ([A guide to streaming SSR with React 18 - LogRocket Blog](https://blog.logrocket.com/streaming-ssr-with-react-18/#:~:text=This%20rendering%20function%20fully%20integrates,incrementally%20based%20on%20data%20requirements))。

- **Server Components (RSC) のストリーミング場合**: サーバコンポーネントの仕組みでは、サーバから送られてくるのはHTMLではなく**特殊なシリアライズ済みのコンポーネントデータ**です（Reactの用語で「Flight Payload」と呼ばれるものです）。このデータはJSONに似たテキストストリームで、クライアント側で**React専用のデコーダ**によってパースされ、実際のReactコンポーネントツリーに復元されます。具体的には、先述の`createFromReadableStream`関数（`react-server-dom-webpack`パッケージのAPI）がそれを担います ([React Server Components, without a framework?
    
    
    ](https://timtech.blog/posts/react-server-components-rsc-no-framework/#:~:text=%23%23%20%60createFromReadableStream%60%20from%20%60react))。開発者は、例えば以下のような手順でRSCのストリームを受信・利用します:  

  1. クライアントで`fetch('/endpoint/that/returns/rsc')`を呼び出し、サーバコンポーネントのReadableStreamレスポンスを取得する。  
  2. そのReadableStreamを`ReactFlightClient.createFromReadableStream(...)`（Flightプロトコルのクライアント実装）に渡す。  
  3. 戻り値として**Thenable（Promiseのようなもの）**が得られるので、それを使用してサーバコンポーネントのツリーを取得する。React 18.3以降では`React.use()`フックと組み合わせ、Suspenseで囲むことで非同期に読み込まれたコンポーネントを表示することができます ([GitHub - devongovett/rsc-html-stream: Inject an RSC payload into an HTML stream and read it back](https://github.com/devongovett/rsc-html-stream#:~:text=let%20data%3B%20function%20Content%28%29%20,use%28data))。  

  簡単な例を示します。サーバが`/react-payload`というエンドポイントでサーバコンポーネント`<Profile server={true}>`の結果をストリームとして返すとします。クライアント側コードは以下のようになります:

  ```jsx
  import { createFromReadableStream } from 'react-server-dom-webpack/client';
  import { Suspense } from 'react';
  // ...
  const response = await fetch('/react-payload?user=123');
  const stream = response.body;
  const serverTreePromise = createFromReadableStream(stream);
  
  // Suspenseを使ってサーバコンポーネントを非同期にレンダリング
  function ProfilePage() {
    return (
      <Suspense fallback={<div>Loading profile...</div>}>
        {/* use()フックでThenableを読み込む (React 18.3+ 仮想コード) */}
        {React.use(serverTreePromise)}
      </Suspense>
    );
  }
  ReactDOM.createRoot(document.getElementById('root')).render(<ProfilePage />);
  ```  

  上記のコードでは、`createFromReadableStream(response.body)`が**サーバからのストリームを受け取り、Thenableなコンポーネントツリーを返す**部分です。この値をReact 18.3以降で利用可能な`React.use()`（現時点では試験的）の中で使用することで、サーバコンポーネントが解決されるまでSuspenseのfallbackを表示し、解決後に自動でコンテンツが差し込まれます ([GitHub - devongovett/rsc-html-stream: Inject an RSC payload into an HTML stream and read it back](https://github.com/devongovett/rsc-html-stream#:~:text=let%20data%3B%20function%20Content%28%29%20,use%28data))。なお、`React.use()`が使えない場合は`serverTreePromise.then(tree => {...})`で通常のPromiseとして扱い、解決後に明示的にレンダーする方法もあります。  

要するに、Web Streams APIのReadableStreamは、React Concurrent Streamingにおいて**サーバ→クライアント間でUI情報を運ぶパイプ**となっています。React 18のSSR機能は、そのパイプを通じてHTMLやコンポーネントデータを逐次送り出し、クライアントではそれを逐次受け取ってレンダリング・ハイドレーションすることで、高速かつスムーズなユーザ体験を実現します。

# 具体的なコード例

ここでは、Web Streams APIとReact Concurrent Streamingを組み合わせた実装を、具体的なコード例で示します。Express.jsによるSSRストリーミングの例、Fetch APIでのデータストリーミングの例、そしてServer ComponentsとWeb Streams APIを併用したデモの順に紹介します。

## Express.jsを使ったStreaming SSRの実装

まず、Node.js上のExpressサーバでReact 18のストリーミングSSRを行うコード例です。`renderToPipeableStream`を利用し、生成されたNode.jsストリームをHTTPレスポンスにパイプしてクライアントへ送ります（Reactアプリ内でSuspenseを使っていれば自動的にストリーミング挙動になります）。

```js
import express from 'express';
import ReactDOMServer from 'react-dom/server';
import App from './App';  // サーバサイドレンダリングするReactアプリ

const server = express();

server.get('*', (req, res) => {
  const { pipe } = ReactDOMServer.renderToPipeableStream(<App />, {
    onShellReady() {
      // エラーフラグに応じてステータス設定（略）
      res.status(200).setHeader('Content-Type', 'text/html');
      // Reactから返されたストリームをHTTPレスポンスにパイプ
      pipe(res);
    },
    onError(err) {
      console.error('SSR Error:', err);
    }
  });
});

server.listen(3000);
```  

上記のコードでは、`renderToPipeableStream(<App />, {...})`を呼び出すと、即座に`pipe`関数（Nodeストリームをパイプするための関数）と`abort`関数等が得られます。`onShellReady`コールバックは、「シェル」（最初に送信可能なHTML部分）が準備できたタイミングで呼ばれます ([A guide to streaming SSR with React 18 - LogRocket Blog](https://blog.logrocket.com/streaming-ssr-with-react-18/#:~:text=When%20the%20content%20above%20the,piping%20it%20into%20the%20response))。ここでHTTPヘッダをセットし、`pipe(res)`でレスポンスストリームに流し込みを開始します ([A guide to streaming SSR with React 18 - LogRocket Blog](https://blog.logrocket.com/streaming-ssr-with-react-18/#:~:text=let%20didError%20%3D%20false%3B%20const,type%27%2C%20%27text%2Fhtml%27%29%3B%20stream.pipe%28res))（なお、`bootstrapScripts`オプションでクライアントのJSを先行読み込みさせることもできます ([A guide to streaming SSR with React 18 - LogRocket Blog](https://blog.logrocket.com/streaming-ssr-with-react-18/#:~:text=,))）。こうすることで、`<App />`の内容はクライアントへ**ストリーミング送信**されます。`<App />`内部でSuspenseによる遅延コンテンツがある場合、Reactは`onShellReady`時点で用意できたHTMLだけをまず送信し、残りは裏で準備が整い次第ストリーム経由で追加送信します ([A guide to streaming SSR with React 18 - LogRocket Blog](https://blog.logrocket.com/streaming-ssr-with-react-18/#:~:text=When%20the%20content%20above%20the,piping%20it%20into%20the%20response)) ([A guide to streaming SSR with React 18 - LogRocket Blog](https://blog.logrocket.com/streaming-ssr-with-react-18/#:~:text=After%20that%2C%20the%20stream%20will,compared%20to%20its%20former%20version))。  

従来の`renderToString`や`renderToNodeStream`と異なり、`renderToPipeableStream`は**HTMLを最後までバッファしない**ため、サーバ上で非同期データ待ちがあってもその部分を除いた出力を先に送れます ([A guide to streaming SSR with React 18 - LogRocket Blog](https://blog.logrocket.com/streaming-ssr-with-react-18/#:~:text=The%20most%20significant%20change%20compared,not%20suffer%20from%20these%20limitations))。以上のコードにより、ExpressサーバでのストリーミングSSRが実現できます。

## Fetch APIのReadableStreamを利用したデータストリーミング

次に、クライアント側JavaScriptでFetch APIのReadableStreamを扱う例を示します。ここではサーバからイベントデータを逐次受け取って表示するようなシナリオを仮定します。サーバは`text/event-stream`などで逐次メッセージ（例: JSON文字列）を送り、クライアントでそれをストリーム処理します。

```js
// クライアント側: Fetchでサーバからストリームレスポンスを取得
const response = await fetch('/api/stream-events');
if (!response.ok || !response.body) {
  throw new Error('ストリーム取得失敗');
}
const stream = response.body.pipeThrough(new TextDecoderStream());  // バイトストリーム -> テキストストリーム変換
const reader = stream.getReader();

while (true) {
  const { value, done } = await reader.read();
  if (done) break;
  console.log('受信イベントデータ:', value);
  // ここで必要に応じてJSONパース: JSON.parse(value) など
}
console.log('すべてのイベント受信完了');
```  

このコードでは、`fetch`で取得したレスポンスの`body`からReadableStreamを取得し、`TextDecoderStream`でテキストに変換しつつ`getReader()`でリーダーを取得しています。`reader.read()`を繰り返し呼ぶことで**サーバから送られてきたチャンクを1つずつ取り出し**ています ([ReadableStream - Web APIs | MDN](https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream#:~:text=fetch%28,getReader)) ([ReadableStream - Web APIs | MDN](https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream#:~:text=const%20response%20%3D%20await%20fetch%28,let%20total%20%3D%200))。`done`が`true`になればストリームの終端です。ここでは単にコンソールに出力していますが、実際には各`value`（テキストデータ）をJSONデコードして画面に追加描画する、といった処理が行えます。  

上記のようにFetch APIのReadableStreamを使うことで、クライアントアプリケーションでも**逐次データ処理**が可能になります。例えばチャットアプリで新着メッセージをストリーミング受信したり、プログレスバー付きで大きなファイルをダウンロードしたりといった実装がシンプルに書けます。Streams APIにより、ネットワーク越しのデータも**イベント駆動的に少しずつ処理**でき、ユーザにすぐフィードバックを返すことができます。

## Server ComponentsとWeb Streams APIを組み合わせたデモ

最後に、ReactのServer Components (RSC) とWeb Streams APIを組み合わせて利用する高度なデモンストレーションです。ここではサーバコンポーネントの結果を初期HTMLと一緒にストリーミングし、クライアントで復元する手法の概略を示します。  

**サーバ側**: Reactの実験的なRSCレンダラー（`react-server-dom-webpack`等）を用いてサーバコンポーネントをストリームとして生成し、それを通常のHTMLストリームに埋め込みます ([探索未来前端渲染新纪元：rsc-html-stream详解与应用推广-CSDN博客](https://blog.csdn.net/gitblog_00639/article/details/141809228#:~:text=%E6%AD%A4%E9%A1%B9%E7%9B%AE%E5%9F%BA%E4%BA%8E%E4%B8%A4%E4%B8%AA%E5%85%B3%E9%94%AE%E7%9A%84%E6%8A%80%E6%9C%AF%E6%A0%88%EF%BC%9AReact%20Server%20Components%20%E5%92%8C%20%E5%8F%AF%E8%AF%BB%E6%B5%81%EF%BC%88Readable,%E6%96%B9%E6%B3%95%E7%9B%B4%E6%8E%A5%E4%BB%8E%E6%9C%8D%E5%8A%A1%E5%99%A8%E4%BC%A0%E9%80%92%E7%9A%84%E6%95%B0%E6%8D%AE%E6%9E%84%E5%BB%BAReact%E6%A0%91%EF%BC%8C%E8%BF%99%E4%B8%80%E5%88%87%E9%83%BD%E5%9C%A8%E4%B8%80%E4%B8%AA%E4%BC%98%E9%9B%85%E7%9A%84%E5%BC%82%E6%AD%A5%E6%B5%81%E7%A8%8B%E4%B8%AD%E5%AE%8C%E6%88%90%EF%BC%8C%E6%9E%81%E5%A4%A7%E6%8F%90%E5%8D%87%E4%BA%86%E5%89%8D%E5%90%8E%E7%AB%AF%E7%9A%84%E5%8D%8F%E8%B0%83%E6%95%88%E7%8E%87%E3%80%82))。具体的には次のような処理手順になります:

1. **サーバコンポーネントのレンダリング** – 例えば`renderToReadableStream(<App />)`を呼び出し、サーバコンポーネント`<App />`の**Flightデータストリーム**（ReadableStream）を取得します ([GitHub - devongovett/rsc-html-stream: Inject an RSC payload into an HTML stream and read it back](https://github.com/devongovett/rsc-html-stream#:~:text=import%20%7BrenderToReadableStream%7D%20from%20%27react,stream%2Fserver))。このストリームにはUIツリーがテキストデータとして逐次書き出されてきます。  
2. **HTMLストリームへの統合** – 続いて、上記のFlightストリームを**二手に分岐**します（`stream.tee()`を使用 ([GitHub - devongovett/rsc-html-stream: Inject an RSC payload into an HTML stream and read it back](https://github.com/devongovett/rsc-html-stream#:~:text=%2F%2F%20Fork%20the%20stream%2C%20and,use%28data%29%3B))）。一方のストリームはそのまま保持し、もう一方は特殊な`<script>`タグを差し込むTransformStreamを通して**HTML出力に注入**します ([探索未来前端渲染新纪元：rsc-html-stream详解与应用推广-CSDN博客](https://blog.csdn.net/gitblog_00639/article/details/141809228#:~:text=%E6%AD%A4%E9%A1%B9%E7%9B%AE%E5%9F%BA%E4%BA%8E%E4%B8%A4%E4%B8%AA%E5%85%B3%E9%94%AE%E7%9A%84%E6%8A%80%E6%9C%AF%E6%A0%88%EF%BC%9AReact%20Server%20Components%20%E5%92%8C%20%E5%8F%AF%E8%AF%BB%E6%B5%81%EF%BC%88Readable,%E6%96%B9%E6%B3%95%E7%9B%B4%E6%8E%A5%E4%BB%8E%E6%9C%8D%E5%8A%A1%E5%99%A8%E4%BC%A0%E9%80%92%E7%9A%84%E6%95%B0%E6%8D%AE%E6%9E%84%E5%BB%BAReact%E6%A0%91%EF%BC%8C%E8%BF%99%E4%B8%80%E5%88%87%E9%83%BD%E5%9C%A8%E4%B8%80%E4%B8%AA%E4%BC%98%E9%9B%85%E7%9A%84%E5%BC%82%E6%AD%A5%E6%B5%81%E7%A8%8B%E4%B8%AD%E5%AE%8C%E6%88%90%EF%BC%8C%E6%9E%81%E5%A4%A7%E6%8F%90%E5%8D%87%E4%BA%86%E5%89%8D%E5%90%8E%E7%AB%AF%E7%9A%84%E5%8D%8F%E8%B0%83%E6%95%88%E7%8E%87%E3%80%82))。このTransformStream（例えば`injectRSCPayload`という実装）は、HTMLの適切な箇所にシリアライズ済みコンポーネントデータを埋め込む役割を果たします ([探索未来前端渲染新纪元：rsc-html-stream详解与应用推广-CSDN博客](https://blog.csdn.net/gitblog_00639/article/details/141809228#:~:text=%E6%AD%A4%E9%A1%B9%E7%9B%AE%E5%9F%BA%E4%BA%8E%E4%B8%A4%E4%B8%AA%E5%85%B3%E9%94%AE%E7%9A%84%E6%8A%80%E6%9C%AF%E6%A0%88%EF%BC%9AReact%20Server%20Components%20%E5%92%8C%20%E5%8F%AF%E8%AF%BB%E6%B5%81%EF%BC%88Readable,%E6%96%B9%E6%B3%95%E7%9B%B4%E6%8E%A5%E4%BB%8E%E6%9C%8D%E5%8A%A1%E5%99%A8%E4%BC%A0%E9%80%92%E7%9A%84%E6%95%B0%E6%8D%AE%E6%9E%84%E5%BB%BAReact%E6%A0%91%EF%BC%8C%E8%BF%99%E4%B8%80%E5%88%87%E9%83%BD%E5%9C%A8%E4%B8%80%E4%B8%AA%E4%BC%98%E9%9B%85%E7%9A%84%E5%BC%82%E6%AD%A5%E6%B5%81%E7%A8%8B%E4%B8%AD%E5%AE%8C%E6%88%90%EF%BC%8C%E6%9E%81%E5%A4%A7%E6%8F%90%E5%8D%87%E4%BA%86%E5%89%8D%E5%90%8E%E7%AB%AF%E7%9A%84%E5%8D%8F%E8%B0%83%E6%95%88%E7%8E%87%E3%80%82))。最後に通常の`ReactDOMServer.renderToReadableStream(<Content/>)`を実行し、上記TransformStreamで処理することで**RSCデータが埋め込まれたHTMLストリーム**を得ます ([GitHub - devongovett/rsc-html-stream: Inject an RSC payload into an HTML stream and read it back](https://github.com/devongovett/rsc-html-stream#:~:text=data%20%3F%3F%3D%20createFromReadableStream%28s1%29%3B%20return%20React,)) ([GitHub - devongovett/rsc-html-stream: Inject an RSC payload into an HTML stream and read it back](https://github.com/devongovett/rsc-html-stream#:~:text=%2F%2F%20Inject%20the%20RSC%20stream,pipeThrough%28injectRSCPayload%28s2))。  
3. **クライアントへの送信** – 完成したHTMLストリーム（ReadableStream）をHTTPレスポンスとしてクライアントに送ります。HTML中には埋め込み用の複数の`<script>`タグが挿入されており、RSCのペイロードが分割格納されています ([GitHub - devongovett/rsc-html-stream: Inject an RSC payload into an HTML stream and read it back](https://github.com/devongovett/rsc-html-stream#:~:text=A%20small%20utility%20to%20inject,on%20the%20client))。

**クライアント側**: ブラウザはまず送られてきたHTMLを解析・表示します。埋め込みの`<script>`タグには通常実行されないタイプ（例えば`type="x-component"`など）でRSCペイロードが入っているとします。クライアントのJSはそれらを検出して内容を取得・連結し、**もとのReadableStreamに復元**します ([探索未来前端渲染新纪元：rsc-html-stream详解与应用推广-CSDN博客](https://blog.csdn.net/gitblog_00639/article/details/141809228#:~:text=%E6%AD%A4%E9%A1%B9%E7%9B%AE%E5%9F%BA%E4%BA%8E%E4%B8%A4%E4%B8%AA%E5%85%B3%E9%94%AE%E7%9A%84%E6%8A%80%E6%9C%AF%E6%A0%88%EF%BC%9AReact%20Server%20Components%20%E5%92%8C%20%E5%8F%AF%E8%AF%BB%E6%B5%81%EF%BC%88Readable,%E6%96%B9%E6%B3%95%E7%9B%B4%E6%8E%A5%E4%BB%8E%E6%9C%8D%E5%8A%A1%E5%99%A8%E4%BC%A0%E9%80%92%E7%9A%84%E6%95%B0%E6%8D%AE%E6%9E%84%E5%BB%BAReact%E6%A0%91%EF%BC%8C%E8%BF%99%E4%B8%80%E5%88%87%E9%83%BD%E5%9C%A8%E4%B8%80%E4%B8%AA%E4%BC%98%E9%9B%85%E7%9A%84%E5%BC%82%E6%AD%A5%E6%B5%81%E7%A8%8B%E4%B8%AD%E5%AE%8C%E6%88%90%EF%BC%8C%E6%9E%81%E5%A4%A7%E6%8F%90%E5%8D%87%E4%BA%86%E5%89%8D%E5%90%8E%E7%AB%AF%E7%9A%84%E5%8D%8F%E8%B0%83%E6%95%88%E7%8E%87%E3%80%82))。`rsc-html-stream`というライブラリでは、これを行う`rscStream`という仮想的なReadableStreamが提供されており、サーバで埋め込んだのと逆順に**HTMLから取り出したデータをReadableStreamとして再構成**します ([GitHub - devongovett/rsc-html-stream: Inject an RSC payload into an HTML stream and read it back](https://github.com/devongovett/rsc-html-stream#:~:text=On%20the%20client%2C%20use%20the,the%20HTML%20by%20the%20server))。最後に、その再構成されたRSCストリームを`ReactServerDOMReader.createFromReadableStream()`に渡すことで、元の`<App />`コンポーネントツリーがクライアント上に復元されます ([探索未来前端渲染新纪元：rsc-html-stream详解与应用推广-CSDN博客](https://blog.csdn.net/gitblog_00639/article/details/141809228#:~:text=%E6%AD%A4%E9%A1%B9%E7%9B%AE%E5%9F%BA%E4%BA%8E%E4%B8%A4%E4%B8%AA%E5%85%B3%E9%94%AE%E7%9A%84%E6%8A%80%E6%9C%AF%E6%A0%88%EF%BC%9AReact%20Server%20Components%20%E5%92%8C%20%E5%8F%AF%E8%AF%BB%E6%B5%81%EF%BC%88Readable,%E6%96%B9%E6%B3%95%E7%9B%B4%E6%8E%A5%E4%BB%8E%E6%9C%8D%E5%8A%A1%E5%99%A8%E4%BC%A0%E9%80%92%E7%9A%84%E6%95%B0%E6%8D%AE%E6%9E%84%E5%BB%BAReact%E6%A0%91%EF%BC%8C%E8%BF%99%E4%B8%80%E5%88%87%E9%83%BD%E5%9C%A8%E4%B8%80%E4%B8%AA%E4%BC%98%E9%9B%85%E7%9A%84%E5%BC%82%E6%AD%A5%E6%B5%81%E7%A8%8B%E4%B8%AD%E5%AE%8C%E6%88%90%EF%BC%8C%E6%9E%81%E5%A4%A7%E6%8F%90%E5%8D%87%E4%BA%86%E5%89%8D%E5%90%8E%E7%AB%AF%E7%9A%84%E5%8D%8F%E8%B0%83%E6%95%88%E7%8E%87%E3%80%82))。復元後は通常のReactコンポーネントと同様に`ReactDOM.hydrateRoot`等でHydrationしてインタラクティブにします。

以上の流れにより、**追加のHTTPリクエストなし**にサーバコンポーネントのデータを初期レンダリングに織り交ぜることができます ([探索未来前端渲染新纪元：rsc-html-stream详解与应用推广-CSDN博客](https://blog.csdn.net/gitblog_00639/article/details/141809228#:~:text=rsc,%E6%A0%87%E7%AD%BE%E5%AE%9E%E7%8E%B0%EF%BC%8C%E4%BB%8E%E8%80%8C%E5%8A%A0%E9%80%9F%E4%BA%86%E9%A1%B5%E9%9D%A2%E7%9A%84%E5%88%9D%E6%AC%A1%E6%B8%B2%E6%9F%93%E5%92%8C%E5%90%8E%E7%BB%AD%E4%BA%A4%E4%BA%92%E3%80%82)) ([探索未来前端渲染新纪元：rsc-html-stream详解与应用推广-CSDN博客](https://blog.csdn.net/gitblog_00639/article/details/141809228#:~:text=,%E6%96%B9%E6%B3%95%E7%9B%B4%E6%8E%A5%E4%BB%8E%E6%9C%8D%E5%8A%A1%E5%99%A8%E4%BC%A0%E9%80%92%E7%9A%84%E6%95%B0%E6%8D%AE%E6%9E%84%E5%BB%BAReact%E6%A0%91%EF%BC%8C%E8%BF%99%E4%B8%80%E5%88%87%E9%83%BD%E5%9C%A8%E4%B8%80%E4%B8%AA%E4%BC%98%E9%9B%85%E7%9A%84%E5%BC%82%E6%AD%A5%E6%B5%81%E7%A8%8B%E4%B8%AD%E5%AE%8C%E6%88%90%EF%BC%8C%E6%9E%81%E5%A4%A7%E6%8F%90%E5%8D%87%E4%BA%86%E5%89%8D%E5%90%8E%E7%AB%AF%E7%9A%84%E5%8D%8F%E8%B0%83%E6%95%88%E7%8E%87%E3%80%82))。この手法では、従来はクライアントが別途fetchで取得していたサーバコンポーネント情報を、**SSRのHTML内に直接バンドル**するため初回ロードが高速化します ([探索未来前端渲染新纪元：rsc-html-stream详解与应用推广-CSDN博客](https://blog.csdn.net/gitblog_00639/article/details/141809228#:~:text=%E5%AE%A2%E6%88%B7%E7%AB%AF%E4%BB%8EHTML%E4%B8%AD%E8%AF%BB%E5%8F%96%E5%87%BA%E6%9D%A5%EF%BC%8C%E8%BD%AC%E6%8D%A2%E5%9B%9E%E6%B5%81%E7%9A%84%E5%BD%A2%E5%BC%8F%E3%80%82%E8%BF%99%E4%B8%80%E5%88%9B%E6%96%B0%E6%96%B9%E6%B3%95%E7%9C%81%E5%8E%BB%E4%BA%86%E5%AE%A2%E6%88%B7%E7%AB%AF%E9%A2%9D%E5%A4%96%E7%9A%84HTTP%E8%AF%B7%E6%B1%82%EF%BC%8C%E5%B0%86%E5%8E%9F%E6%9C%AC%E9%9C%80%E8%A6%81%E5%8D%95%E7%8B%AC%E8%AF%B7%E6%B1%82%E7%9A%84RSC%E6%95%B0%E6%8D%AE%E7%9B%B4%E6%8E%A5%E5%B5%8C%E5%85%A5%E5%88%9D%E5%A7%8BHTML%E5%93%8D%E5%BA%94%E4%B8%AD%EF%BC%8C%E9%80%9A%E8%BF%87%E4%B8%80%E7%B3%BB%E5%88%97%60))。一方で実装は高度であり、先述の`rsc-html-stream`のような支援ライブラリが登場しています。Reactの公式ソリューションとしてはNext.js 13以降で同様のアプローチ（RSCのインライン化）が採用されており、開発者が意識せずともこの仕組みが内部で動くようになっています。

上記デモはかなり発展的な内容ですが、**Server ComponentsのストリーミングとWeb Streams APIの強力な組み合わせ**を示す好例です。サーバ・クライアント間でUIを構成するデータをストリーム送受信し、途切れなくユーザにページを提供できる点で、Web Streams APIはReactの最新SSR技術を支える不可欠なパーツとなっています。

# 応用事例と最適化

最後に、Web Streams APIとReact Concurrent Streamingを活用した**応用シーンやパフォーマンス最適化**について解説します。

**● SSRのパフォーマンス最適化**:  
ストリーミングSSRは、伝統的なSSRに比べて大きなパフォーマンス上の利点があります。最大の利点は**TTFB（Time To First Byte）の短縮**です。レンダリング開始後、最初のバイトがクライアントに届くまでの時間が大幅に早くなり、ページサイズにかかわらず一定して速い傾向があります ([Streaming Server-Side Rendering](https://www.patterns.dev/react/streaming-ssr/#:~:text=Streaming%20aims%20to%20improve%20the,and%20provides%20the%20following%20benefits))。React 18のストリーミングでは、ページ全体がレンダリング完了するのを待たずに送信を開始できるため、ユーザは非常に早い段階で何らかのコンテンツを目にできます ([Streaming Server-Side Rendering](https://www.patterns.dev/react/streaming-ssr/#:~:text=Streaming%20aims%20to%20improve%20the,and%20provides%20the%20following%20benefits)) ([Mastering Server-Side Rendering (SSR) in React 19 with Vite: The Ultimate Guide for Developers - DEV Community](https://dev.to/yugjadvani/mastering-server-side-rendering-ssr-in-react-19-with-vite-the-ultimate-guide-for-developers-4mgm#:~:text=,sooner%2C%20even%20on%20slower%20connections))。特にSuspenseを駆使することで、**重要なAbove-the-fold（ファーストビュー）コンテンツ**を即座に表示しつつ、重たい部分は後から読み込み・描画するといった戦略が可能です。これによりユーザビリティが向上し、体感速度の大幅な改善につながります ([Mastering Server-Side Rendering (SSR) in React 19 with Vite: The Ultimate Guide for Developers - DEV Community](https://dev.to/yugjadvani/mastering-server-side-rendering-ssr-in-react-19-with-vite-the-ultimate-guide-for-developers-4mgm#:~:text=,sooner%2C%20even%20on%20slower%20connections))。実際、「ユーザがコンテンツを目にするまでの時間が短縮され、たとえ低速回線でも早期にページ構造が見えてくる」という報告があります ([Mastering Server-Side Rendering (SSR) in React 19 with Vite: The Ultimate Guide for Developers - DEV Community](https://dev.to/yugjadvani/mastering-server-side-rendering-ssr-in-react-19-with-vite-the-ultimate-guide-for-developers-4mgm#:~:text=,sooner%2C%20even%20on%20slower%20connections))。さらに、React 18のSSRは**複数のデータソースの待ち合わせ（ウォーターフォール現象）**を解消するため、あるコンポーネントのデータ待ちで他の部分のレンダリングが遅れることがありません ([A guide to streaming SSR with React 18 - LogRocket Blog](https://blog.logrocket.com/streaming-ssr-with-react-18/#:~:text=This%20rendering%20function%20fully%20integrates,incrementally%20based%20on%20data%20requirements))。結果として**サーバの処理・ネットワーク送信・クライアント描画が並行的**に進み、全体として効率が上がります。

**● Web Streams APIを活用した効率的なデータ転送**:  
Web Streams API自体もアプリケーションの効率を高めます。ストリーム処理ではデータをチャンクに分割して逐次処理するため、巨大なデータセットでも**メモリ消費を抑えつつ処理**できます ([Node.js Streams vs. Web Streams API | Better Stack Community](https://betterstack.com/community/guides/scaling-nodejs/nodejs-streams-vs-web-streams-api/#:~:text=It%27s%20common%20for%20applications%20to,the%20the%20application%20being%20killed))。例えば数GBに及ぶファイルを読み書きする場合でも、ストリームを使えば数KB～数MB単位で分割して処理するため、サーバ・クライアント双方でメモリフットプリントを低く保てます ([Node.js Streams vs. Web Streams API | Better Stack Community](https://betterstack.com/community/guides/scaling-nodejs/nodejs-streams-vs-web-streams-api/#:~:text=It%27s%20common%20for%20applications%20to,the%20the%20application%20being%20killed))。また、ネットワーク伝送においてもストリームは**遅延を隠蔽**し、パイプライン上で処理を重ねることで**帯域を有効活用**します。送信側ではデータを送り終える前に次のデータ生成に取りかかれ、受信側では到着した分からすぐパースやレンダリングにかかれます。たとえば圧縮ストリーム（CompressionStream）を使えばサーバでオンザフライ圧縮しつつ送信し、クライアントで逐次展開しながら処理する、といったことも可能で、これにより**通信量削減とリアルタイム処理**を両立できます。さらにWeb Streams APIの**バックプレッシャー制御**によって、受信側が詰まっているときは送信側が待機するため、無駄なデータ再送やバッファ溢れを防ぐことができます。総じて、Web Streams APIを適切に用いることで、データ転送のスループット向上とリソース効率化が図れます。

**● React Suspenseとの組み合わせによるUX向上**:  
Suspenseは、ストリーミングSSRやサーバコンポーネントと組み合わせることで強力なUX改善手段となります。従来、SSRでは全データが揃うまで完全なUIを出せないため、ユーザは白紙のページかローディングインジケータだけを見せられて待たされるケースがありました。Suspenseを用いたストリーミングでは、**ロード中の部分にフォールバックUI（ローディングスピナー等）**を表示しつつ、他の部分は即座に本物のコンテンツを表示できます。これによりユーザには「ページの一部は表示されており、残りも読み込み中である」ことが視覚的に伝わり、心理的な待ち時間の負担が軽減します。実際、React 18のHTMLストリーミングはSuspense境界ごとにチャンクを送るため、**画面が段階的に構成されていく体験**を提供できます ([Enhancing SSR with React 18: A deep dive into server side rendering optimisation | Equal Experts](https://www.equalexperts.com/blog/tech-focus/enhancing-ssr-with-react-18-a-deep-dive-into-server-side-rendering-optimisation/#:~:text=%E2%80%98rendertopipeablestream%E2%80%99%2C%20HTML%20is%20streamed%20in,enhancing%20performance%20and%20rendering%20speed))。また、クライアント側のSelective Hydrationと組み合わせれば、ユーザがすぐ操作したいUI（例えばメニューや記事一覧）は即座にインタラクティブになり、読み込み中の部分（詳細記事の本文など）は後からインタラクティブになる、といった**段階的エンハンスメント**も可能です。これは大きなUX向上ポイントで、重いページでもまず骨格をすぐ使える状態にし、徐々に肉付けするような動的振る舞いが実現します。

---

以上、Web Streams APIとReact Concurrent Streaming (RCS) の関係性について、概要から具体例・応用まで詳しく解説しました。**Web Streams APIは高速・効率的なデータ逐次処理の基盤**を提供し、**ReactのConcurrent Streamingはそれを活かしてユーザ体験を向上させるSSR技術**です。両者を適切に組み合わせることで、現代的なウェブアプリケーションにおいてスピーディーかつリソースフレンドリーなサーバレンダリングを実現できるでしょう。その結果、ユーザはより快適にコンテンツへアクセスでき、開発者はパフォーマンスと開発容易性を両立させることができます。

**参考文献・資料**: Web Streams API（MDN） ([ReadableStream - Web APIs | MDN](https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream#:~:text=The%20,of%20a%20%2054%20object)) ([TransformStream - Web APIs | MDN](https://developer.mozilla.org/en-US/docs/Web/API/TransformStream#:~:text=It%20may%20be%20passed%20to,stream%20from%20XML%20to%20JSON))、React 18 ストリーミングSSR公式ブログ ([A guide to streaming SSR with React 18 - LogRocket Blog](https://blog.logrocket.com/streaming-ssr-with-react-18/#:~:text=The%20most%20significant%20change%20compared,not%20suffer%20from%20these%20limitations)) ([A guide to streaming SSR with React 18 - LogRocket Blog](https://blog.logrocket.com/streaming-ssr-with-react-18/#:~:text=This%20rendering%20function%20fully%20integrates,incrementally%20based%20on%20data%20requirements))、LogRocket解説記事 ([A guide to streaming SSR with React 18 - LogRocket Blog](https://blog.logrocket.com/streaming-ssr-with-react-18/#:~:text=let%20didError%20%3D%20false%3B%20const,type%27%2C%20%27text%2Fhtml%27%29%3B%20stream.pipe%28res)) ([A guide to streaming SSR with React 18 - LogRocket Blog](https://blog.logrocket.com/streaming-ssr-with-react-18/#:~:text=When%20the%20content%20above%20the,piping%20it%20into%20the%20response))、その他関連ドキュメント・記事 ([renderToPipeableStream – React](https://react.dev/reference/react-dom/server/renderToPipeableStream#:~:text=This%20API%20is%20specific%20to,runtimes%2C%20should%20use%20renderToReadableStream%20instead)) ([探索未来前端渲染新纪元：rsc-html-stream详解与应用推广-CSDN博客](https://blog.csdn.net/gitblog_00639/article/details/141809228#:~:text=%E6%AD%A4%E9%A1%B9%E7%9B%AE%E5%9F%BA%E4%BA%8E%E4%B8%A4%E4%B8%AA%E5%85%B3%E9%94%AE%E7%9A%84%E6%8A%80%E6%9C%AF%E6%A0%88%EF%BC%9AReact%20Server%20Components%20%E5%92%8C%20%E5%8F%AF%E8%AF%BB%E6%B5%81%EF%BC%88Readable,%E6%96%B9%E6%B3%95%E7%9B%B4%E6%8E%A5%E4%BB%8E%E6%9C%8D%E5%8A%A1%E5%99%A8%E4%BC%A0%E9%80%92%E7%9A%84%E6%95%B0%E6%8D%AE%E6%9E%84%E5%BB%BAReact%E6%A0%91%EF%BC%8C%E8%BF%99%E4%B8%80%E5%88%87%E9%83%BD%E5%9C%A8%E4%B8%80%E4%B8%AA%E4%BC%98%E9%9B%85%E7%9A%84%E5%BC%82%E6%AD%A5%E6%B5%81%E7%A8%8B%E4%B8%AD%E5%AE%8C%E6%88%90%EF%BC%8C%E6%9E%81%E5%A4%A7%E6%8F%90%E5%8D%87%E4%BA%86%E5%89%8D%E5%90%8E%E7%AB%AF%E7%9A%84%E5%8D%8F%E8%B0%83%E6%95%88%E7%8E%87%E3%80%82)) ([GitHub - devongovett/rsc-html-stream: Inject an RSC payload into an HTML stream and read it back](https://github.com/devongovett/rsc-html-stream#:~:text=A%20small%20utility%20to%20inject,on%20the%20client))など。