跪拜 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 ↗