Integrating vercel ai sdk n4n.ai models into a Next.js App Router project takes about ten minutes if you treat the gateway as an OpenAI-compatible backend. The Vercel AI SDK’s OpenAI provider adapter works unchanged against any endpoint that speaks the /v1/chat/completions contract, so you only swap the base URL and API key.
Step 1: Scaffold a Next.js App Router project
Start with a clean TypeScript App Router skeleton. The --app flag is the critical part; everything else is taste.
npx create-next-app@latest my-chat-app --ts --app --no-tailwind --no-eslint
cd my-chat-app
We skip Tailwind and ESLint to keep the diff focused. You can layer styling on later. The command produces an app/ directory with layout.tsx and page.tsx. Route handlers and client components will live here.
Step 2: Install the Vercel AI SDK and OpenAI compatibility layer
The SDK separates core streaming logic from provider bindings. You need both the ai package and @ai-sdk/openai.
npm install ai @ai-sdk/openai
@ai-sdk/openai exports createOpenAI, a factory that returns a provider object. That provider accepts a baseURL override, which is the only mechanism required to retarget the SDK at a different inference endpoint.
Step 3: Configure environment variables for the gateway
Create .env.local at the project root. The server reads two values: the gateway base URL and a secret key.
# .env.local
N4N_API_KEY=sk-your-gateway-key
N4N_BASE_URL=https://api.n4n.ai/v1
Do not prefix these with NEXT_PUBLIC_. Next.js exposes .env.local only to server-side code, and the key must never reach the browser. If you deploy to Vercel, copy the same keys into the project’s environment settings.
Step 4: Create a server-side API route that streams chat completions
App Router route handlers execute on the server, making them the correct place to hold the API key and call the model. Create app/api/chat/route.ts.
// app/api/chat/route.ts
import { createOpenAI } from '@ai-sdk/openai';
import { streamText } from 'ai';
const gateway = createOpenAI({
baseURL: process.env.N4N_BASE_URL,
apiKey: process.env.N4N_API_KEY,
});
export async function POST(req: Request) {
const { messages } = await req.json();
const result = streamText({
model: gateway('gpt-4o-mini'), // any model id the gateway exposes
messages,
});
return result.toDataStreamResponse();
}
The gateway instance is a provider bound to the external base URL. Model strings pass through verbatim, so you can request any of the 240+ model ids the gateway addresses. If an upstream provider is rate-limited or degraded, the gateway performs automatic fallback before the stream reaches your route. The toDataStreamResponse() call serializes the output using the Vercel AI SDK data stream protocol, which the client hook consumes without custom parsing.
You can add a system prompt or sampling parameters directly in streamText:
const result = streamText({
model: gateway('claude-3-5-sonnet'),
system: 'You are a terse debugging assistant.',
temperature: 0.2,
messages,
});
Step 5: Build a client chat component with useChat
Create app/components/chat.tsx. The useChat hook manages message state, input binding, and streaming consumption.
'use client';
import { useChat } from 'ai/react';
export function Chat() {
const { messages, input, handleInputChange, handleSubmit, status } = useChat({
api: '/api/chat',
});
return (
<div>
<div>
{messages.map((m) => (
<div key={m.id}>
<strong>{m.role}: </strong>
{m.content}
</div>
))}
</div>
<form onSubmit={handleSubmit}>
<input
value={input}
onChange={handleInputChange}
disabled={status !== 'ready'}
placeholder="Ask something…"
/>
<button type="submit">Send</button>
</form>
</div>
);
}
The status field lets you disable the input while a response streams. In production you would add error boundaries and maybe abort handling, but the wire format is correct as written.
Step 6: Wire up the page and run the dev server
Replace app/page.tsx with a server component that renders the chat.
// app/page.tsx
import { Chat } from './components/chat';
export default function Page() {
return (
<main>
<h1>Chat</h1>
<Chat />
</main>
);
}
Start the dev server:
npm run dev
Open http://localhost:3000. You should see the heading and an input box. No network calls happen until you submit.
Step 7: Verify the integration end-to-end
Two independent checks confirm the stack works: a raw HTTP call and a browser interaction.
First, curl the route directly to confirm streaming without the UI:
curl -X POST http://localhost:3000/api/chat \
-H "Content-Type: application/json" \
-d '{"messages":[{"role":"user","content":"Say hello in one word"}]}'
A successful response streams frames prefixed with 0: containing tokens. A 401 means the key isn’t loaded; a 404 means the file isn’t at app/api/chat/route.ts. If you see a valid stream, the server side is correct.
Second, type a message in the browser. The useChat hook appends assistant tokens as they arrive. Open DevTools → Network, filter for /api/chat, and inspect the request headers: it should be a POST with Accept: text/x-vercel-ai-data-stream. The response type should match.
The gateway provides per-token usage metering, so you can inspect the x-usage response header (or the final data stream frame) to see exact prompt and completion tokens charged.
Handling model routing directives
The gateway honors client routing directives and forwards provider cache-control hints, so you can pin a provider or enable prompt caching by injecting headers at the provider level:
const gateway = createOpenAI({
baseURL: process.env.N4N_BASE_URL,
apiKey: process.env.N4N_API_KEY,
headers: {
'x-n4n-routing': 'provider:anthropic',
'x-n4n-cache': 'enabled',
},
});
This routes the request to a specific backend and signals that the prompt is eligible for provider-side caching, reducing latency and cost on repeated prefixes. The rest of the SDK code stays identical.
Step 8: Swap models and harden for production
Because the model string is passed through, changing models is a one-line edit in route.ts. You can even make the model dynamic from the client:
// in route.ts
const { messages, model } = await req.json();
const result = streamText({
model: gateway(model ?? 'gpt-4o-mini'),
messages,
});
Then extend useChat with body: { model: 'claude-3-5-sonnet' }.
For production, move the gateway instantiation outside the handler to avoid rebuilding the provider per request, add export const runtime = 'edge' if you want edge streaming, and ensure your deployment platform injects N4N_API_KEY as a secret. The Vercel AI SDK’s data stream is edge-compatible by default.
That’s the full path from create-next-app to a streaming chat UI backed by any model the gateway exposes, with fallback and metering handled upstream.