跪拜 Guibai
← All articles
JavaScript

Streaming LLM Responses in Vue3 Without Third-Party Libraries

By mONESY ·
Read original on juejin.cn ↗ Google Translate ↗ Alt translation

Dependency-free streaming keeps bundle sizes small and avoids the maintenance drag of third-party SSE clients. The buffer-based fragment reassembly pattern applies to any LLM API that emits Server-Sent Events, not just DeepSeek.

Summary

Streaming text from large language models no longer requires pulling in axios or dedicated SSE libraries. A single Vue3 component built on the Composition API wires up DeepSeek's chat endpoint using only the browser's built-in Fetch and ReadableStream APIs. A checkbox toggles the `stream` parameter, letting the same request function branch between blocking JSON responses and incremental delta parsing.

The core streaming loop reads binary chunks from `response.body.getReader()`, decodes them with TextDecoder, and filters for SSE `data:` lines. A running buffer catches truncated JSON fragments that span network packets, preventing parse errors that would otherwise break the typewriter effect. Each valid delta is appended to a reactive `ref`, which Vue uses to update the DOM in real time.

Environment variables keep the API key out of source control, and the walkthrough covers common pitfalls: CORS workarounds via Vite proxy or a backend relay, Chinese character encoding, and fallback logic for browsers that lack ReadableStream support.

Takeaways
Fetch and ReadableStream alone can drive a full SSE streaming client with no external dependencies.
A single `stream` boolean toggles the request body parameter and branches the response handler between blocking JSON and incremental delta parsing.
A TextDecoder instance converts Uint8Array chunks to UTF-8 strings, preventing garbled multi-byte characters.
A persistent buffer variable catches JSON fragments split across network packets and retries parsing after the next chunk arrives.
The `data: [DONE]` sentinel line terminates the read loop; without it, the loop relies on the native stream's `done` flag.
Vite environment variables (`import.meta.env.VITE_*`) keep API keys out of the codebase, and a local proxy or backend relay sidesteps browser CORS restrictions.
Conclusions

Many front-end developers reach for SSE client libraries to avoid the boilerplate of ReadableStream, but the boilerplate is small enough that the dependency may not be worth the abstraction.

The buffer-reassembly pattern is the only genuinely tricky part of the whole flow; everything else is standard Fetch and string manipulation.

Toggling between streaming and non-streaming modes in the same function makes the component self-contained for demos, but a production setup would likely split these into separate concerns for error handling and retry logic.

Concepts & terms
SSE (Server-Sent Events)
A standard that allows a server to push real-time updates to the browser over a single HTTP connection. Each message is a line prefixed with `data: `, and the stream ends with a `data: [DONE]` line.
ReadableStream
A browser API representing a stream of binary data that can be read in chunks. `response.body.getReader()` returns a reader whose `read()` method yields successive `{ value: Uint8Array, done: boolean }` objects.
TextDecoder
A browser API that decodes a stream of bytes (Uint8Array) into a string using a specified character encoding, typically UTF-8. Essential for handling multi-byte characters that may be split across network packets.
Source: juejin.cn ↗ Google Translate ↗ Backup ↗