Streaming AI Output from Scratch with Vue 3 and DeepSeek
Streaming is the dividing line between a clunky AI UI and a polished product. Knowing how to consume a raw byte stream, parse server-sent tokens, and handle partial data is now a baseline skill for any frontend developer building on LLM APIs.
The core problem is the standard full-response API pattern: a frontend sends a request, waits seconds for the entire LLM output, and only then renders anything. Streaming output fixes this by keeping an HTTP connection open and pushing each generated token to the client immediately. The browser's ReadableStream API, a TextDecoder, and a buffer to reassemble fragmented chunks turn that stream into a smooth character-by-character display.
A full Vue 3 + Vite implementation is provided, covering environment setup, API key storage via .env.local, and a single-file App.vue component with reactive state, two-way binding on the question input, and a checkbox toggle between streaming and non-streaming modes. The streaming path uses response.body.getReader() to loop over binary chunks, parses DeepSeek's line-delimited JSON format, and stops when the [DONE] sentinel arrives.
Common failure points are addressed directly: 401 errors from misconfigured environment variables, garbled text from wrong decoder encoding, lost content when skipping the buffer, and local CORS workarounds. The piece closes with expansion ideas like conversation history, markdown rendering, and error retries.
The streaming implementation is surprisingly small — about 20 lines of JavaScript — because the browser platform already ships the hard parts (ReadableStream, TextDecoder). Most of the work is understanding the data format and handling edge cases.
Toggling between streaming and non-streaming modes in the same component is a sharp teaching device; it makes the latency difference visceral and proves the streaming path works by direct comparison.
The article treats the buffer as a first-class concern, which is correct: real-world streaming code that skips buffering will fail intermittently and be hard to debug, because chunk boundaries do not align with message boundaries.