The Byte-by-Byte Pipeline That Powers Every AI Typing Effect
Every AI chat UI relies on streaming, but most developers treat it as a black box. Getting the byte-boundary and event-boundary handling wrong produces garbled text, broken JSON parsing, or silent truncation — bugs that are hard to reproduce because they depend on network chunking. This walkthrough makes the pipeline explicit so teams can debug it rather than guess at it.
A non-streaming LLM call returns one JSON blob; a streaming call returns a raw byte stream that the browser must parse into incremental text. The Fetch API exposes this as a ReadableStream, whose `getReader()` yields Uint8Array chunks that arrive at unpredictable boundaries — a single `read()` might contain half an SSE event, one complete event, or fragments of several. A working frontend pipeline therefore needs a TextDecoder in streaming mode to handle multi-byte UTF-8 characters split across chunks, and a buffer that splits on SSE double-newline delimiters while preserving trailing partial events for the next read.
The article walks through a complete Vue 3 implementation: a `parseSSEEvent` function that extracts `data:` payloads, recognizes `[DONE]`, and fires a callback with each `delta.content`; a `readSSEStream` loop that feeds the decoder and buffer together; and a reactive `content.value += delta` that produces the typewriter effect without timers. It also explains why `fetch()` beats `EventSource` for chat (POST, custom headers, cancellation) and why SSE beats WebSocket for one-directional text generation.
A companion piece on the BFF server side is promised, but the client-side takeaway is already complete: the illusion of live typing is just a disciplined pipeline across byte, event, and security boundaries.
Many developers assume streaming is just a loop over `response.body`, but the real work is handling the mismatch between network chunk boundaries and application-level event boundaries.
The choice of `fetch()` over `EventSource` is not stylistic — it's a hard requirement driven by POST, authentication headers, and the need to cancel in-flight requests when users regenerate or switch conversations.
SSE's simplicity is a feature, not a limitation: for the dominant LLM use case of one question → one streaming answer, WebSocket's bidirectional overhead buys nothing.
The three-boundary model (byte, event, security) generalizes beyond Vue and Fetch to any frontend stack consuming streaming LLM APIs.