DEV Community

Cover image for Bridging n8n AI Workflows to React 19 with Vercel AI SDK and SSE Streaming
Ken
Ken

Posted on

Bridging n8n AI Workflows to React 19 with Vercel AI SDK and SSE Streaming

When integrating n8n-io/n8n (v2.38+) into modern web frontends, a common architecture is using n8n as an autonomous agent orchestrator while serving UI over React 19 and @ai-sdk/react. However, connecting n8n's Webhook streaming node directly to a client typewriter interface exposes subtle protocol mismatches.

By default, n8n emits chunked HTTP or standard Server-Sent Events (SSE). If consumed naively in useChat, reverse proxies (Nginx/Cloudflare) buffer chunks until a 4KB boundary is reached, destroying the real-time typewriter effect. Furthermore, unhandled connection drops or manual stream cancellations leave dangling execution threads on the n8n runner.

The Gateway Route Adapter

To normalize the stream for Vercel AI SDK and ensure client-driven cancellation propagates downstream, place a lightweight App Router edge route between React 19 and your n8n instance:

// app/api/chat/route.ts
import { createDataStreamResponse } from 'ai';

export const runtime = 'edge';

export async function POST(req: Request) {
  const { messages } = await req.json();
  const controller = new AbortController();
  req.signal.addEventListener('abort', () => controller.abort());

  return createDataStreamResponse({
    execute: async (dataStream) => {
      const upstream = await fetch(process.env.N8N_WEBHOOK_URL!, {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
          'X-Accel-Buffering': 'no', // Disable upstream Nginx proxy buffering
        },
        body: JSON.stringify({ messages }),
        signal: controller.signal,
      });

      if (!upstream.ok || !upstream.body) {
        throw new Error(`n8n upstream rejected with status ${upstream.status}`);
      }

      const reader = upstream.body.getReader();
      const decoder = new TextDecoder();

      while (true) {
        const { done, value } = await reader.read();
        if (done) break;

        const chunk = decoder.decode(value, { stream: true });
        // Pipe normalized token chunk into the AI SDK text stream
        dataStream.writeText(chunk);
      }
    },
  });
}
Enter fullscreen mode Exit fullscreen mode

React 19 Optimistic Client Integration

On the client side, bind @ai-sdk/react directly. React 19's concurrent rendering prevents UI hitching during rapid burst emissions:

'use client';

import { useChat } from '@ai-sdk/react';

export function AgentChat() {
  const { messages, input, handleInputChange, handleSubmit, isLoading, stop } = useChat({
    api: '/api/chat',
  });

  return (
    <div className="flex flex-col h-full max-w-2xl mx-auto p-4">
      <div className="flex-1 overflow-y-auto space-y-3">
        {messages.map((m) => (
          <div key={m.id} className={m.role === 'user' ? 'text-right font-medium' : 'text-left text-slate-800'}>
            {m.content}
          </div>
        ))}
      </div>
      <form onSubmit={handleSubmit} className="mt-4 flex gap-2">
        <input value={input} onChange={handleInputChange} placeholder="Prompt workflow..." className="border p-2 flex-1 rounded" />
        {isLoading ? (
          <button type="button" onClick={stop} className="px-4 py-2 bg-red-600 text-white rounded">Stop</button>
        ) : (
          <button type="submit" className="px-4 py-2 bg-black text-white rounded">Send</button>
        )}
      </form>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

Operational Takeaway

Always forward req.signal to n8n's incoming fetch. Without this handshake, clicking "Stop" in the client aborts the local connection while n8n continues wasting LLM tokens executing orphaned background nodes.

Demonstrates connecting Vercel AI SDK chat interfaces directly to B-Lost's unbuffered SSE relay, eliminating reverse-proxy buffering delays.


Disclosure: Multi-model API relays and compute for this evaluation are sponsored by b-lost.com — an AI gateway offering 0.8x official pricing, native prompt caching, and zero user-data retention. All observations reflect independent developer testing.

Top comments (0)