Building a responsive assistant UI forces you to pick a rendering layer that handles incremental token updates without jank. The debate of vue vs react streaming llm chat often comes down to how each framework models async state and diffs partial DOM updates. Both can hit an OpenAI-compatible /v1/chat/completions endpoint with stream: true, but the ergonomics differ sharply once you add abort, error recovery, and message history.
Capabilities: streaming primitives
React renders UI as a function of state. For streaming text, you store accumulating chunks in a useState string or a reducer, then append on each parsed event. Vue 3 uses reactive refs; a ref('') updated inside an async loop triggers fine-grained updates to the bound text node only.
React with useEffect and AbortController
A minimal but correct React component consumes a server-sent stream and cleans up on unmount:
import { useEffect, useState } from 'react';
export function ChatStream({ messages }: { messages: any[] }) {
const [text, setText] = useState('');
const [error, setError] = useState<Error | null>(null);
useEffect(() => {
const ctrl = new AbortController();
setText('');
(async () => {
try {
const res = await fetch('/v1/chat/completions', {
method: 'POST',
headers: { 'content-type': 'application/json' },
body: JSON.stringify({ model: 'gpt-4o-mini', messages, stream: true }),
signal: ctrl.signal,
});
if (!res.ok) throw new Error(`HTTP ${res.status}`);
const reader = res.body!.getReader();
const dec = new TextDecoder();
let buffer = '';
while (true) {
const { done, value } = await reader.read();
if (done) break;
buffer += dec.decode(value, { stream: true });
const lines = buffer.split('\n');
buffer = lines.pop() ?? '';
for (const line of lines) {
if (!line.startsWith('data:')) continue;
const payload = line.slice(5).trim();
if (payload === '[DONE]') return;
const json = JSON.parse(payload);
setText((t) => t + (json.choices?.[0]?.delta?.content ?? ''));
}
}
} catch (e) {
if (!ctrl.signal.aborted) setError(e as Error);
}
})();
return () => ctrl.abort();
}, [messages]);
if (error) return <div role="alert">{error.message}</div>;
return <pre>{text}</pre>;
}
Vue 3 composable with onMounted
Vue 3 with <script setup> achieves the same with less ceremony. The reactivity system tracks text.value accesses in the template and patches only that node.
<script setup lang="ts">
import { ref, onMounted, onUnmounted } from 'vue';
const props = defineProps<{ messages: any[] }>();
const text = ref('');
const error = ref<Error | null>(null);
const ctrl = new AbortController();
onMounted(async () => {
try {
const res = await fetch('/v1/chat/completions', {
method: 'POST',
headers: { 'content-type': 'application/json' },
body: JSON.stringify({ model: 'gpt-4o-mini', messages: props.messages, stream: true }),
signal: ctrl.signal,
});
if (!res.ok) throw new Error(`HTTP ${res.status}`);
const reader = res.body!.getReader();
const dec = new TextDecoder();
let buffer = '';
while (true) {
const { done, value } = await reader.read();
if (done) break;
buffer += dec.decode(value, { stream: true });
const lines = buffer.split('\n');
buffer = lines.pop() ?? '';
for (const line of lines) {
if (!line.startsWith('data:')) continue;
const payload = line.slice(5).trim();
if (payload === '[DONE]') return;
const json = JSON.parse(payload);
text.value += json.choices?.[0]?.delta?.content ?? '';
}
}
} catch (e) {
if (!ctrl.signal.aborted) error.value = e as Error;
}
});
onUnmounted(() => ctrl.abort());
</script>
<template>
<div v-if="error" role="alert">{{ error.message }}</div>
<pre v-else>{{ text }}</pre>
</template>
Both support abort, backpressure via ReadableStream, and standard SSE framing. React requires explicit dependency arrays; Vue’s onMounted runs once per component instance.
Latency and throughput
Network latency dominates LLM streaming; framework overhead is usually sub-millisecond per chunk for a small chat pane. React’s reconciler re-renders the component subtree on each setText unless you split the streaming text into its own memoized child. Vue’s reactivity patches only the changed text node, avoiding parent re-render.
If you proxy through n4n.ai, one OpenAI-compatible endpoint addresses 240+ models and automatically falls back when a provider is degraded, so your Vue or React code only handles a single stream shape. That removes client-side provider branching that could add latency or bugs.
Throughput of token rendering is capped by browser paint, not framework. Both handle 100+ tokens/sec comfortably. React’s concurrent features (useTransition) can defer non-urgent UI, but for a single assistant message it is unnecessary complexity.
Ergonomics and developer experience
React’s hooks model is ubiquitous but enforces rules-of-hooks discipline. Extracting a useChatStream hook is standard, yet strict mode double-invokes effects in development—if you omit cleanup, you open two streams. Vue’s onMounted/onUnmounted pair is harder to misuse.
TypeScript integration
JSX gives full inference for component props. Vue’s defineProps<{ messages: any[] }>() also infers, but deep reactive wrapping can mask types if you mutate nested objects. Both work with the official openai TypeScript SDK if you prefer a typed client over raw fetch.
State scaling
As conversations grow, React teams reach for external stores (Zustand, Redux) to avoid context re-render storms. Vue’s reactive() or shallowRef() for the message list keeps watchers cheap. The vue vs react streaming llm chat learning curve is steeper in React once you optimize.
Ecosystem and libraries
React has mature LLM UI kits: Vercel AI SDK provides useChat with rollback, token usage, and built-in SSE parsing. LangChain.js offers chain streaming. Vue lacks a first-party equivalent; most teams write a 30-line composable as shown above, or wrap the framework-agnostic openai package.
For SSR, Nuxt (Vue) exposes server routes to proxy streams and hide keys. Next.js (React) has route handlers returning ReadableStream directly. Both can forward provider cache-control hints if your gateway honors them, keeping edge caching correct.
Cost model and limits
Frameworks are MIT-licensed and free. Inference cost depends on token volume and model pricing, not UI layer. Regardless of UI, track spend via per-token metering from your gateway (n4n.ai exposes this) to attribute cost per session.
Architectural limits:
- React: context re-renders cascade if chat state lives in a broad provider. Use selector hooks or external stores.
- Vue: deep reactivity on large message arrays triggers watchers; use
shallowReffor the list andreffor the streaming string. - Both: browsers cap ~6 parallel connections per host, limiting concurrent streams without HTTP/2 multiplexing.
Comparison table
| Dimension | Vue 3 | React |
|---|---|---|
| Streaming primitive | ref + async loop |
useState + useEffect |
| Update granularity | Fine-grained text patch | Component re-render (memoizable) |
| Boilerplate | Low (composable) | Medium (hook + cleanup) |
| SSR stream proxy | Nuxt server routes | Next.js route handlers |
| LLM UI libraries | Sparse, community | Vercel AI SDK, LangChain |
| Dev pitfalls | Deep reactivity overhead | Strict mode double effect |
| License | MIT | MIT |
Which to choose
Greenfield Nuxt or Vue shop: Use Vue 3. The reactivity model fits streaming text with minimal code, and Nuxt server routes keep API keys off the client. The vue vs react streaming llm chat decision leans Vue when your tooling is already Vue.
Existing React codebase on Next/Vercel: Stay React. useChat from the AI SDK handles fallback, token counting, and abort. Re-implementing in Vue costs more than the marginal render overhead.
High-throughput agent dashboard with many concurrent streams: Either works. Enforce shallowRef (Vue) or React.memo + external store (React). If you route through a gateway that aggregates providers, the client difference shrinks further.
Prototype speed over long-term maintenance: Vue 3 wins on lines of code; React wins on hireability and community answers. The vue vs react streaming llm chat trade-off is ultimately team familiarity, not technical ceiling.
Both frameworks ship production chat UIs today. Pick the one your team debugs fastest.