n4nAI

Building a full-stack chatbot with the Vercel AI SDK and Next.js

Step-by-step tutorial for building a vercel ai sdk nextjs full-stack chatbot with streaming, tool calls, and an OpenAI-compatible provider. Learn to wire routes, hooks, and UI.

n4n Team2 min read528 words

Audio narration

Coming soon — every post will get a voice note here.

Prerequisites

  • Node.js 18.18 or later
  • Next.js 14 with the App Router (stable, not canary)
  • npm or pnpm
  • An API key from OpenAI, or any OpenAI-compatible endpoint. If you want a single base URL that fronts 240+ models with automatic fallback when a provider is degraded, n4n.ai works as a drop-in replacement.
  • Working knowledge of TypeScript, React, and HTTP

Why the Vercel AI SDK

A vercel ai sdk nextjs full-stack chatbot keeps frontend and backend in one codebase while the SDK handles Server-Sent Events, message normalization, and React state. You write a route that streams tokens; the useChat hook gives you messages, input, and handleSubmit without custom WebSocket code or manual fetch parsing. The alternative—hand-rolling streaming parsers—is error-prone and steals time from product work.

1. Scaffold and install

npx create-next-app@latest chatbot --ts --app --tailwind --src-dir --import-alias "@/*"
cd chatbot
npm install ai @ai-sdk/openai zod

This produces src/app, Tailwind CSS, and the AI packages. @ai-sdk/openai implements the OpenAI chat protocol but accepts a custom baseURL, so the same code runs against OpenAI, Azure, or a gateway.

Expected project layout after install:

src/
  app/
    api/chat/route.ts   (to create)
    page.tsx
    layout.tsx
  components/
    chat.tsx            (to create)

2. Configure the provider

Create .env.local:

# Direct OpenAI
OPENAI_API_KEY=sk-...

# Or an OpenAI-compatible gateway:
# OPENAI_BASE_URL=https://api.n4n.ai/v1
# OPENAI_API_KEY=your-gateway-key

When OPENAI_BASE_URL is set, the provider routes all traffic there. The vercel ai sdk nextjs full-stack chatbot logic stays identical. No conditional imports, no feature flags.

3. Build the streaming API route

Create src/app/api/chat/route.ts:

import { openai } from '@ai-sdk/openai';
import { streamText } from 'ai';

export const runtime = 'edge';

export async function POST(req: Request) {
  const { messages } = await req.json();

  const result = streamText({
    model: openai('gpt-4o-mini'),
    messages,
  });

  return result.toDataStreamResponse();
}

streamText accepts the standard chat messages array ({role, content}) and returns a result object. toDataStreamResponse() serializes it as a streaming HTTP response that the client hook consumes. The edge runtime keeps cold starts low for simple proxies.

Checkpoint—curl the route before writing UI:

curl -X POST http://localhost:3000/api/chat \
  -H "Content-Type: application/json" \
  -d '{"messages":[{"role":"user","content":"Say hello in one word"}]}'

Expected raw output (truncated):

data: {"type":"text-start"}
data: {"type":"text","value":"Hi"}
data: {"type":"text-end"}
data: {"type":"finish","finishReason":"stop"}

The exact token varies, but the data: framing confirms streaming works.

4. Client chat component

Create src/components/chat.tsx:

'use client';

import { useChat } from 'ai/react';

export function Chat() {
  const { messages, input, handleInputChange, handleSubmit, isLoading } =
    useChat();

  return (
    <div className="mx-auto max-w-lg p-4">
      <div className="space-y-2">
        {messages.map((m) => (
          <div key={m.id} className="whitespace-pre-wrap">
            <span className="font-bold">{m.role}: </span>
            {m.content}
          </div>
        ))}
      </div>

      <form onSubmit={handleSubmit} className="mt-4 flex gap-2">
        <input
          className="flex-1 rounded border p-2"
          value={input}
          onChange={handleInputChange}
          placeholder="Type a message"
          disabled={isLoading}
        />
        <button
          type="submit"
          className="rounded bg-black px-4 py-2 text-white"
          disabled={isLoading}
        >
          Send
        </button>
      </form>
    </div>
  );
}

useChat POSTs to /api/chat by default and appends streamed tokens to messages. This hook is the backbone of a vercel ai sdk nextjs full-stack chatbot: it manages input state, submission, and reconciliation of partial responses.

5. Mount the page

Edit src/app/page.tsx:

import { Chat } from '@/components/chat';

export default function Page() {
  return (
    <main className="min-h-screen py-10">
      <h1 className="mb-6 text-center text-2xl font-semibold">Chatbot</h1>
      <Chat />
    </main>
  );
}

Run npm run dev. Open http://localhost:3000, type “What is 2+2?”, and the assistant message renders token by token. The network tab shows a single POST with a 200 status and a chunked body.

6. Add a tool call

Production chatbots invoke actions. Extend the route with a weather tool:

import { openai } from '@ai-sdk/openai';
import { streamText, tool } from 'ai';
import { z } from 'zod';

export async function POST(req: Request) {
  const { messages } = await req.json();

  const result = streamText({
    model: openai('gpt-4o-mini'),
    messages,
    tools: {
      getWeather: tool({
        parameters: z.object({ city: z.string() }),
        execute: async ({ city }) => {
          // Stub; replace with real API
          return { city, tempC: 21 };
        },
      }),
    },
  });

  return result.toDataStreamResponse();
}

The SDK streams a tool invocation, runs execute server-side, and feeds the result back to the model. On the client, message.toolInvocations exposes the call and result if you want to render a “Calling weather…” badge.

Sample UI exchange after adding the tool:

user: What's the weather in Berlin?
assistant: The temperature in Berlin is 21°C.

7. Inspect the stream protocol

The data stream is not raw text. Each line is a JSON object with a type. Relevant types:

{"type":"text-start"}
{"type":"text","value":"The"}
{"type":"text","value":" temperature"}
{"type":"tool-invocation","toolInvocation":{"toolName":"getWeather","args":{"city":"Berlin"}}}
{"type":"tool-result","toolInvocation":{"toolName":"getWeather","result":{"tempC":21}}}
{"type":"finish","finishReason":"stop","usage":{"promptTokens":38,"completionTokens":9}}

Understanding this helps when debugging proxy layers or writing custom clients.

8. Production notes

  • Switch runtime to 'nodejs' if you need filesystem or specific Node APIs; edge is fine for pure streaming proxies.
  • Lock the route with auth by reading a session cookie in POST and returning 401 before calling streamText.
  • If you route through n4n.ai, you can send provider cache-control hints via request headers; the gateway forwards them and meters per-token usage without extra code.
  • The useChat hook accepts api: '/custom/path' and onError for centralized error reporting.

That is a complete vercel ai sdk nextjs full-stack chatbot: one route, one hook, one component. Add persistence with a database or RAG with embeddings once the prototype proves its worth—not before.

Tagsvercel-ai-sdknextjschatbottutorial

Written by

n4n Team

The team building n4n — a single OpenAI-compatible API in front of 240+ models, with automatic fallback, load balancing and pay-per-token metering.

More from n4n Team →

All vercel ai sdk deep dive posts →