title: "How to stream OpenAI API response chunks from: 開発体験で変わるAIツール選定"
description: "FastAPIからNext.js 14 App RouterへSSEでOpenAIのストリーミングレスポンスを渡す実装パターンを解説。バックエンド・フロントエンド間のデータフロー設計、エラーハンドリング、運用上の注意点を整理します。"
meta_description: "FastAPI × Next.js 14 App Router × SSEでOpenAI APIのストリーミングを実装する方法。チャンク転送の設計、fallback処理、運用監視まで実装視点で解説します。"
date: 2026-06-07
category: 開発ツール
tags: [python, next.js, fastapi, openai-api, server-sent-events]
何が出たのか
2026年6月初旬、Stack Overflowに「FastAPIで受け取ったOpenAI APIのストリーミングレスポンスを、Next.js 14のApp RouterフロントエンドへServer-Sent Events(SSE)経由で渡すにはどうすればよいか」という質問が投稿されました(views: 58、answers: 1)。
この質問が注目されるのは、構成要素の組み合わせが現在の実務でよく見られるスタックだからです。OpenAI APIのストリーミング(stream=True)、FastAPIの非同期ジェネレータ、Next.js 14のApp RouterにおけるRSC(React Server Components)とクライアントコンポーネントの境界、SSEのブラウザ実装——これらを一度に正しく組み合わせるのは、個々の仕様を把握していても意外と詰まりやすいポイントです。
同時期のHacker NewsやRedditのML系スレッドでも、「LLMのストリーミングをどこで処理させるか」という設計上の議論が活発で、バックエンドにPythonを置くアーキテクチャとNext.jsのRoute Handlers(旧API Routes)で完結させるアーキテクチャの比較が繰り返し話題になっています。本記事では、この実装パターンの技術的な論点を整理します。
開発体験で変わる点
ストリーミングがUIに与える体感の差
OpenAI APIをstream=Trueで呼び出すと、モデルの生成結果がトークン単位で順次返ってきます。これを使わずに完了を待つ場合、ユーザーは数秒から十数秒の無応答を経験します。ストリーミングを使うと、最初のトークンが届いた時点でUIへの描画が始まるため、体感的な待ち時間が大きく変わります。
この差は、チャットUIやコード補完UIのように「生成中であることをユーザーに見せたい」ケースで特に効きます。
SSEを選ぶ理由
ブラウザからサーバーへの一方向リアルタイム通信には、WebSocketとSSEの2択がよく挙げられます。LLMのストリーミング用途では、以下の理由からSSEが選ばれることが多いです。
- プロトコルの軽量さWebSocketは双方向通信のためのハンドシェイクとフレーミングを持ちますが、SSEはHTTP上で動作し、
text/event-streamのContent-Typeで通常のHTTPレスポンスとして扱えます。 - 再接続の自動処理ブラウザのEventSource APIは切断時の再接続を自動で行います。
- インフラ側の透過性多くのリバースプロキシやCDNがHTTPをそのまま通すため、WebSocketのような特別な設定が不要なケースが多いです。
ただし、SSEは単方向(サーバー→クライアント)のみです。ユーザーが生成途中でキャンセルするような操作を実装する場合は、別途HTTPリクエスト(AbortController)を組み合わせる必要があります。
既存の流れとの違い
Next.js単体で完結させる場合との比較
Next.js 14のRoute Handlersは、ReadableStreamを返すことでSSEを直接実装できます。OpenAIの公式Node.js SDKもストリーミングをサポートしているため、フロントエンドとバックエンドをNext.jsで統一するアーキテクチャは十分成立します。
FastAPIを間に挟む構成が選ばれる主な理由は次のとおりです。
- 既存のPythonバックエンドへの統合社内にPythonで書かれたデータ処理ロジックや認証基盤がある場合、それをFastAPIに乗せてOpenAI呼び出しも同じサービスに集約できます。
- モデル切り替えの柔軟性バックエンドでOpenAI以外のLLM(ローカルモデル、Azure OpenAI、Anthropicなど)を差し替えやすく、フロントエンドのコードを変えずに済みます。
- レート制限・ログの一元管理APIキーの管理、リクエストのロギング、レート制限の実装をPython側に集約できます。
一方、FastAPIを挟むことで、チャンクがブラウザに届くまでのホップが1つ増えます。FastAPIが非同期ジェネレータを正しく使っていない場合、OpenAIからのチャンクをバッファリングしてしまい、ストリーミングの恩恵が失われます。
実装の核心:非同期ジェネレータとStreamingResponse
FastAPI側の実装で最も重要なのは、StreamingResponseと非同期ジェネレータの組み合わせです。
```python
from fastapi import FastAPI
from fastapi.responses import StreamingResponse
from openai import AsyncOpenAI
app = FastAPI()
client = AsyncOpenAI()
async def stream_openai(prompt: str):
response = await client.chat.completions.create(
model="gpt-4o",
messages=[{"role": "user", "content": prompt}],
stream=True,
)
async for chunk in response:
delta = chunk.choices[0].delta.content
if delta:
SSEフォーマット: "data: <内容>\n\n"
yield f"data: {delta}\n\n"
yield "data: [DONE]\n\n"
@app.get("/stream")
async def stream_endpoint(prompt: str):
return StreamingResponse(
stream_openai(prompt),
media_type="text/event-stream",
)
```
async for chunk in responseの部分でOpenAIのSDKが非同期イテレータとして機能します。yieldでチャンクを逐次返すことで、FastAPIはバッファリングせずにレスポンスを流し続けます。
Next.js 14のApp Router側では、クライアントコンポーネントからfetchでこのエンドポイントを呼び、response.bodyをReadableStreamとして読み取ります。
```typescript
// app/components/StreamingChat.tsx (Client Component)
"use client";
import { useState } from "react";
export default function StreamingChat() {
const [output, setOutput] = useState("");
const handleStream = async () => {
const res = await fetch("http://localhost:8000/stream?prompt=Hello");
const reader = res.body!.getReader();
const decoder = new TextDecoder();
while (true) {
const { done, value } = await reader.read();
if (done) break;
const text = decoder.decode(value);
// "data: " プレフィックスとSSE区切り文字を除去
const lines = text.split("\n").filter((l) => l.startsWith("data: "));
for (const line of lines) {
const content = line.replace("data: ", "");
if (content !== "[DONE]") setOutput((prev) => prev + content);
}
}
};
return (
<div>
<button onClick={handleStream}>送信</button>
<pre>{output}</pre>
</div>
);
}
```
App RouterのRSC(サーバーコンポーネント)はストリーミングUIの管理には向かないため、この部分は"use client"ディレクティブを付けたクライアントコンポーネントとして実装するのが現実的です。
実装・運用で気になる点
CORSの設定
FastAPIとNext.jsが別オリジンで動く場合(開発環境ではlocalhost:8000とlocalhost:3000など)、FastAPI側でCORSミドルウェアを設定する必要があります。allow_originsに本番のフロントエンドドメインを明示し、ワイルドカード(*)は避けるのが基本です。
```python
from fastapi.middleware.cors import CORSMiddleware
app.add_middleware(
CORSMiddleware,
allow_origins=["https://your-frontend.example.com"],
allow_methods=["GET"],
allow_headers=["*"],
)
```
バッファリングとプロキシの挙動
NginxやCloudflareなどのリバースプロキシを経由する場合、デフォルト設定ではレスポンスをバッファリングしてしまい、SSEが機能しないことがあります。Nginxではproxy_buffering off;とX-Accel-Buffering: noヘッダーの設定が必要です。Vercelにデプロイする場合も、Edge RuntimeとNode.js Runtimeでストリーミングの挙動が異なるため、確認が必要です。
エラーハンドリングとfallback
OpenAI APIはレート制限(429エラー)やサーバーエラー(500系)を返すことがあります。ストリーミング中にエラーが発生した場合、すでにHTTP 200でレスポンスが開始されているため、ステータスコードで通知できません。エラーをSSEのデータとして送る設計(例:data: {"error": "rate_limit_exceeded"}\n\n)にしておき、クライアント側でパースして表示を切り替える実装が必要です。
```python
async def stream_openai(prompt: str):
try:
response = await client.chat.completions.create(...)
async for chunk in response:
...
yield f"data: {delta}\n\n"
except Exception as e:
yield f"data: {{\"error\": \"{str(e)}\"}}\n\n"
finally:
yield "data: [DONE]\n\n"
```
ロギングと監視
ストリーミングエンドポイントは、リクエスト完了までのレイテンシが通常のAPIより長くなります。APM(アプリケーションパフォーマンス監視)ツールでタイムアウト閾値を調整しておかないと、正常なリクエストがアラートを発火させることがあります。また、OpenAIへのトークン使用量はストリーミング完了後にしか確定しないため、コスト監視はusageフィールドを含む最終チャンクをログに記録する設計にします。
APIキーの管理
FastAPIサーバーにOpenAIのAPIキーを持たせる構成では、キーの漏洩リスクをバックエンドに集約できます。フロントエンドからOpenAI APIを直接呼ぶ構成(Next.jsのRoute Handlersで完結させる場合も含む)では、ブラウザのネットワークタブにAPIキーが露出しないよう、必ずサーバーサイドで処理する必要があります。
Spectralの見解
1. 技術的な読み
FastAPI + SSE + Next.js 14 App Routerの構成は、現時点(2026年6月)で実務に十分耐えられる組み合わせです。ただし、「動く」と「プロダクションで安定する」の間には、プロキシのバッファリング設定、エラーをSSEで返す設計、非同期ジェネレータの正しい使い方という3つのギャップがあります。Stack Overflowの質問が示すように、この組み合わせは個々の技術を知っていても詰まりやすく、特にプロキシ層の挙動は環境依存が大きいです。
2. PoCで確認すべき点
- バッファリングの挙動本番に近いインフラ(Nginx、Vercel、Cloud Runなど)でSSEが途切れずに流れるかを早期に確認します。開発環境(
localhost直接接続)では問題なく動いても、プロキシを挟んだ途端に止まるケースがあります。 - エラー時のUXOpenAI APIのレート制限やタイムアウトが発生したとき、ユーザーに何が表示されるかをシナリオとして定義し、SSEのエラーペイロード設計と合わせて検証します。
- トークンコストの計測ストリーミングでは
usageの取得タイミングが変わります。PoCの段階でコスト計測のロジックを確認しておくと、本番移行後の請求の見通しが立てやすくなります。
3. 業務・プロダクト実装に移す時のリスク
- インフラ依存のリスクSSEはHTTP/1.1の接続を長時間維持します。ロードバランサーのアイドルタイムアウト設定が短い環境では、生成途中で接続が切れることがあります。接続維持のためのkeep-aliveチャンク送信(定期的な空データ送出)を実装するか、インフラ側のタイムアウト設定を変更する必要があります。
- スケーリングのリスク長時間接続が多数並行すると、サーバーのコネクション数が増加します。FastAPIのワーカー数とuvicornの設定、必要に応じてHorizontal Pod Autoscalerの閾値を調整します。
- モデル切り替え時の互換性OpenAIのストリーミングAPIのレスポンス形式は、モデルやAPIバージョンによって微妙に変わることがあります。
delta.contentがNoneになるチャンクの扱いなど、防御的なパースを実装しておくと、モデル変更時の影響を局所化できます。
まとめ
FastAPIからNext.js 14 App RouterへOpenAI APIのストリーミングをSSEで渡す構成は、Pythonバックエンドを持つプロダクトにとって現実的な選択肢です。実装の核心は、FastAPIの非同期ジェネレータとStreamingResponseの正しい組み合わせ、Next.jsクライアントコンポーネントでのReadableStream読み取り、そしてSSEフォーマットの一貫した扱いにあります。
一方で、プロキシのバッファリング、エラーハンドリングの設計、長時間接続のスケーリングという3点は、ローカルでの動作確認だけでは見えにくいリスクです。PoCの段階でこれらを本番に近い環境で検証しておくことが、後工程での手戻りを減らす上で効果的です。
ストリーミングUIの実装を検討している場合、まずバックエンドの構成(Next.js完結型かFastAPI分離型か)を決め、その上でインフラ要件を確認するという順序で進めると、設計の見通しが立てやすくなります。
関連論点として transformers: 開発体験で変わるAIツール選定 もあわせて読むと、この技術動向の背景を追いやすくなります。
Spectralでは、技術調査からPoC設計、プロダクト実装まで支援しています。実現性の確認や事業活用の相談は サービス詳細 と お問い合わせ をご覧ください。

Author
森島拓生
Spectral 代表 / AI導入・エージェント設計
Spectral代表。AI Development & Consultingを軸に、非エンジニアとの対話から要件定義を構造化する「上流工程AI」や、AIエージェントによる業務自動化の設計・検証に取り組む。技術を導入して終わらせず、現場で継続して使える運用設計までを重視している。
AI導入について、もっと詳しく知りたい方へ
お問い合わせ