跪拜 Guibai
← All articles
JavaScript · Artificial Intelligence · Full-Stack

The SSE Engineering Inflection Point: Why Your Frontend Needs a BFF Layer

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

API keys baked into frontend bundles are trivially extractable; a BFF layer is the smallest practical step that closes that leak without a full backend rewrite. It also gives frontend teams a server-side surface they control, which is increasingly necessary as AI features move from toy demos to products that handle real credentials and streaming data.

Summary

A direct frontend-to-LLM streaming setup works for a demo but leaks API keys into the client bundle, buries ReadableStream and SSE parsing inside Vue components, and hits same-origin policy walls. Inserting a thin Node.js BFF layer between the browser and the LLM API moves the key to a server-side process, offloads buffer management and stream decoding from the UI, and pairs with a Vite dev-server proxy to make cross-origin requests invisible to the browser.

The BFF is a single Express file that accepts a prompt from the frontend, attaches the secret key, calls the DeepSeek API with `stream: true`, and pipes the response back. A Vite proxy rewrites `/api/*` paths to the BFF's port, so the browser sees only same-origin requests. The result is a cleaner frontend that consumes data without touching binary streams, and a security boundary that keeps credentials out of the JavaScript bundle.

This architecture is a deliberate step in an evolution: first understand raw SSE and buffer mechanics, then wrap them in a server-side layer that frontend engineers own and maintain. The next stage completes the pattern by parsing the SSE stream inside the BFF so the frontend receives only plain text.

Takeaways
API keys stored in Vite's `import.meta.env` are compiled into the client bundle and visible in browser dev tools; moving them to a Node.js BFF process keeps them server-side.
A BFF layer for streaming LLM calls is a single Express route that receives a prompt, attaches the secret key, fetches the LLM API with `stream: true`, and pipes the response back.
Vite's dev-server proxy rewrites `/api/*` requests to the BFF's port, making cross-origin requests appear same-origin to the browser and avoiding CORS errors entirely.
A 502 error during development usually means the BFF server isn't running — the Vite proxy is up, but the upstream Node process on port 3000 is not.
Node.js 18+ includes a native `fetch` API that returns a `ReadableStream`, matching the browser's Web Streams API and letting BFF code handle streaming data without third-party HTTP libraries.
The BFF file lives inside the frontend project repository, is maintained by frontend engineers, and is deliberately lightweight — one file, a few routes, no ORM or heavy framework layers.
Conclusions

SSE is a better fit than WebSocket for LLM streaming because the interaction is inherently one-way: the frontend sends one prompt and the server pushes tokens back; full-duplex adds handshake and heartbeat overhead with no benefit.

The BFF pattern originated from frontend engineers writing their own Node services when backend teams couldn't deliver API changes fast enough — it's an organizational workaround that became an architectural pattern.

Using native `fetch` in Node.js instead of axios or node-fetch eliminates a dependency and keeps the BFF's attack surface small, which matters when the service handles API keys.

The `dotenv.config({ path: ['.env.local', '.env'] })` array syntax creates a priority loading order that mirrors Vite's own env-file resolution, keeping local overrides consistent across frontend and BFF.

The article's code is a deliberate midpoint: it proves the BFF forwarding chain works before adding stream parsing logic, following a layer-by-layer verification strategy rather than building everything at once.

Concepts & terms
BFF (Backend For Frontend)
A thin server layer written and maintained by frontend engineers that sits between the browser and upstream APIs. It handles concerns specific to the frontend experience — aggregating data, managing credentials, and processing streams — without the weight of a traditional backend.
SSE (Server-Sent Events)
An HTTP-based protocol where the server pushes a unidirectional stream of `data:` lines to the browser over a single long-lived connection. Each message is delimited by double newlines, and the stream ends with a `[DONE]` signal. Simpler than WebSocket for one-way data flows like LLM token streaming.
Vite Proxy
A built-in forward proxy in Vite's dev server that intercepts requests matching a path prefix (e.g., `/api`) and forwards them to a different origin. It solves same-origin policy restrictions during development by making cross-origin API calls appear to come from the same port that served the frontend.
Web Streams API (ReadableStream)
A standard for handling streaming data in both browsers and Node.js. A `ReadableStream<Uint8Array>` represents a sequence of binary chunks that can be read incrementally, enabling real-time processing of LLM token streams without buffering the entire response.
Same-Origin Policy
A browser security rule that prevents JavaScript from one origin (protocol + domain + port) from accessing resources from a different origin. It blocks a frontend on `localhost:5173` from directly fetching an API on `localhost:3000`, which is why a proxy or CORS headers are needed during development.
Source: juejin.cn ↗ Google Translate ↗ Backup ↗