Next.js AI 串流路由如何在用戶離開後中止生成,避免持續計費
Your Next.js AI streaming route keeps billing you after the user leaves
Next.js AI 串流路由若未在用戶離開後中止上游模型請求,生成就會繼續至完成,並為用戶看不到的 token 計費。文章建議自行建立 ReadableStream,利用用戶端斷線時觸發的 cancel() 回調呼叫 upstream.abort(),停止後續生成。
An AI chat endpoint feels finished the moment tokens start streaming into the browser. Then your first real bill arrives, and the token count is higher than what your users actually read. The usual reason: responses that keep generating after the user has already closed the tab.
Where the money leaks
In the Next.js App Router, a streaming route opens a connection to the model and pipes tokens back to the browser. If the user navigates away or closes the tab mid-response, the browser drops its connection — but the upstream request to the model keeps generating to completion. You pay for every token it produces, and nobody ever sees them. On long answers with an impatient audience, that waste adds up fast.
The fix: stop generating when the browser disconnects
When you build the SSE stream yourself with a ReadableStream, its cancel() callback fires the moment the client connection drops. That's your hook to abort the upstream model request instead of letting it run to completion:
export function POST(req: Request) {
const upstream = anthropic.messages.stream({ model, max_tokens, messages });
const body = new ReadableStream({
async start(controller) {
for await (const event of upstream) controller.enqueue(encode(event));
controller.close();
},
cancel() {
// The browser went away — stop generating (and paying).
upstream.abort();
},
});
return new Response(body, {
headers: {
"content-type": "text/event-stream",
"cache-control": "no-cache, no-transform",
},
});
}
That one method turns "streaming works in the demo" into "streaming doesn't leak money in production."
Three things that bite you next
- "no-transform" matters. Some proxies buffer SSE, so your "stream" arrives as a single blob at the very end. The Cache-Control no-transform hint tells them to leave it alone.
-
Raise the route's max duration. Long generations outlive the platform's default timeout and get cut off mid-answer; bump
maxDurationon the route. - Meter what you spend. Cancelling saves money you can't see unless you measure it. Record input, output, and cache tokens per request, priced per model, so the savings show up in your numbers instead of your imagination. I wrote up the exact schema I use for that here.
Why it belongs in the foundation
None of this is hard. But it's the kind of thing you only discover you needed after it has already cost you — which is exactly why it should live in the foundation of an AI app, not the backlog.
I package this pattern — along with per-request cost metering, typed tool use, versioned prompts with an eval harness, and plan-gated quotas — into a Next.js 16 + Claude starter kit called Shipwright. The live demo shows the exact cost of every request as you chat.
來源:DEV Community · dev.to