Vercel AI SDK Examples: Practical Patterns for Building AI-Powered Web Apps

If you are looking for Vercel AI SDK examples that go beyond a "hello world" streaming demo, this article walks through the patterns you will actually reach for when building production features: streaming chat, structured output with typed schemas, tool calling, and multi-step agent flows. The AI SDK has become one of the most popular ways to wire large language models into JavaScript and TypeScript applications because it abstracts away provider differences and gives you React hooks out of the box. Rather than listing every function in the API, we will look at concrete, realistic examples you can adapt, along with the mistakes developers commonly make when moving from a prototype to something deployed on Vercel or any other Node.js host.

Getting Set Up: The Minimum Viable Configuration

The AI SDK is a set of packages rather than a single library. The core package, ai, contains the provider-agnostic primitives, while each model provider has its own package such as @ai-sdk/openai, @ai-sdk/anthropic, or @ai-sdk/google. This separation matters: you can swap providers by changing one import instead of rewriting your request handling.

A minimal installation looks like this:

npm install ai @ai-sdk/openai zod

The official AI SDK documentation is the best reference for the current package names, since the ecosystem evolves quickly and provider packages are added regularly.

Once installed, you configure a provider with an API key, typically read from an environment variable:

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

const openai = createOpenAI({
  apiKey: process.env.OPENAI_API_KEY,
});

A common mistake at this stage is hardcoding keys or exposing them in client-side code. The SDK is designed around a split architecture: model calls happen on the server, and the client consumes a stream. Keep your keys in server-side environment variables and never import provider packages into components that run in the browser.

Example 1: Streaming Text Generation in a Route Handler

The most fundamental example is streaming text from a model to the browser. In a Next.js App Router route handler, the streamText function returns a streamable response that the client can consume incrementally:

// app/api/completion/route.ts
import { streamText } from 'ai';
import { createOpenAI } from '@ai-sdk/openai';

const openai = createOpenAI({ apiKey: process.env.OPENAI_API_KEY });

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

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

  return result.toTextStreamResponse();
}

On the client, the useCompletion hook handles the streaming plumbing for you:

'use client';
import { useCompletion } from 'ai/react';

export function Composer() {
  const { completion, input, setInput, handleSubmit, isLoading } =
    useCompletion({ api: '/api/completion' });

  return (
    <form onSubmit={handleSubmit}>
      <textarea value={input} onChange={(e) => setInput(e.target.value)} />
      <button type="submit" disabled={isLoading}>Generate</button>
      <p>{completion}</p>
    </form>
  );
}

The key insight in this example is that streaming is the default, not an add-on. Users perceive streamed responses as dramatically faster even when total generation time is identical, because the first tokens appear within a few hundred milliseconds. If you are building anything user-facing, avoid the temptation to await the full response server-side and send it as JSON — that single decision is the difference between an AI feature that feels responsive and one that feels broken.

Example 2: Chat Interfaces with useChat and Message History

For conversational UIs, the useChat hook is the workhorse. It manages message state, streaming, and history synchronization between client and server:

// app/api/chat/route.ts
import { streamText } from 'ai';
import { createOpenAI } from '@ai-sdk/openai';

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

  const result = streamText({
    model: openai('gpt-4o'),
    system: 'You are a concise assistant for a developer documentation site.',
    messages,
  });

  return result.toDataStreamResponse();
}
'use client';
import { useChat } from 'ai/react';

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

  return (
    <div>
      {messages.map((m) => (
        <div key={m.id}>
          <strong>{m.role}</strong>: {m.content}
        </div>
      ))}
      <form onSubmit={handleSubmit}>
        <input value={input} onChange={(e) => setInput(e.target.value)} />
      </form>
    </div>
  );
}

Two practical considerations here. First, the client sends the full message history with each request, which means token usage grows quadratically in long conversations unless you trim or summarize history. A common production pattern is to cap the number of messages sent, or to persist history in a database and reconstruct it server-side rather than trusting the client. Second, the system prompt belongs in the route handler, not in client-controlled state — otherwise users can trivially override your instructions.

Example 3: Structured Output with Zod Schemas

One of the most valuable patterns in the SDK is structured output: asking a model to return data that conforms to a schema, validated at runtime. This turns fuzzy LLM output into typed, predictable objects you can feed directly into your application logic:

import { generateObject } from 'ai';
import { createOpenAI } from '@ai-sdk/openai';
import { z } from 'zod';

const openai = createOpenAI({ apiKey: process.env.OPENAI_API_KEY });

const extractionSchema = z.object({
  title: z.string(),
  summary: z.string().max(200),
  topics: z.array(z.string()),
  sentiment: z.enum(['positive', 'neutral', 'negative']),
});

export async function analyzeArticle(text: string) {
  const { object } = await generateObject({
    model: openai('gpt-4o'),
    schema: extractionSchema,
    prompt: `Analyze the following article:\n\n${text}`,
  });

  // object is fully typed: { title, summary, topics, sentiment }
  return object;
}

This example demonstrates why the SDK pairs so naturally with Zod: the schema serves three purposes simultaneously — it constrains the model's output format, validates the response at runtime, and generates TypeScript types at compile time. When a model occasionally produces malformed output, the SDK's built-in retry and repair behavior handles re-prompting, which is far more robust than parsing raw JSON strings yourself.

Use generateObject when you need deterministic, machine-readable results (classification, extraction, form autofill) and reserve free-text generation for user-facing prose. Mixing the two — hoping a model's prose happens to be parseable — is one of the most common sources of brittle AI features.

Example 4: Tool Calling and Multi-Step Agents

Tool calling lets a model decide when to invoke your functions, which is the foundation of agents that can query databases, call APIs, or perform actions. Here is a weather-style example using the tool helper:

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

const result = streamText({
  model: openai('gpt-4o'),
  tools: {
    getWeather: tool({
      description: 'Get the current weather for a city',
      parameters: z.object({ city: z.string() }),
      execute: async ({ city }) => {
        const res = await fetch(`https://example-weather.api/?city=${city}`);
        return res.json();
      },
    }),
  },
  maxSteps: 5,
  prompt: 'What is the weather in Tokyo right now?',
});

The maxSteps parameter is what elevates this from single-shot tool use to a multi-step agent loop: the model can call a tool, observe the result, and continue reasoning without returning control to your code. Without it, the stream ends after the first tool call and you must handle continuation manually — a frequent source of confusion for developers who expect the model to "just keep going."

When building tool-based agents, keep the description and parameter schema precise. The model chooses tools based almost entirely on these descriptions, and vague wording leads to wrong-tool selection. Also treat every tool execution as untrusted input to your backend: validate parameters even though they came from a schema, because the model fills them in from user-influenced conversation.

Common Mistakes and Production Considerations

A few failure patterns show up repeatedly when teams ship AI SDK applications:

  • Running model calls in client components. The React hooks are for consuming streams; generation must happen server-side to protect API keys and control usage.
  • Ignoring rate limits and cost. Every streamed token costs money. Add server-side rate limiting and consider caching identical prompts, especially for high-traffic features.
  • Trusting model output. Even with structured output, validate anything that triggers writes to a database or external API. Treat the model as an unreliable junior collaborator, not a trusted service.
  • Overbuilding the first version. A single well-prompted streamText call often solves the problem; reach for agents and multi-step flows only when a single call demonstrably cannot handle it.

Deployment on Vercel works well with this stack because route handlers natively support streaming responses, but the SDK is not Vercel-exclusive — it runs on any Node.js or edge-compatible runtime. If you deploy to serverless platforms, watch out for function execution timeouts on long agent loops, and prefer edge or streaming-friendly configurations for long generations.

Frequently Asked Questions

Is the Vercel AI SDK free to use?

The SDK itself is open source and free. You pay only for the underlying model provider usage (such as OpenAI or Anthropic API costs) and whatever hosting you choose. Vercel’s platform has its own pricing tiers independent of the SDK.

Can I use the AI SDK with providers other than OpenAI?

Yes. The SDK supports many providers through dedicated packages, including Anthropic, Google, Mistral, Amazon Bedrock, and community providers for local models such as Ollama. Because the core API is provider-agnostic, switching usually means changing the model import and identifier.

Does the AI SDK work with frameworks other than Next.js?

Yes. While the React hooks are designed for React-based frameworks, the core package works in any Node.js environment, and there are integrations for Svelte, Vue, and other ecosystems. You can also use the server-side functions without any frontend framework at all.

How do I handle conversation memory across sessions?

The `useChat` hook manages in-memory history during a session, but for persistence you should store messages in a database keyed by a conversation or user ID, then load and pass that history to the model on the server for each request.

What is the difference between generateText and streamText?

`generateText` awaits the complete model response before returning, which is suitable for background tasks and server-side logic. `streamText` returns tokens as they are generated, which is what you want for user-facing interfaces where perceived latency matters.

Conclusion

The Vercel AI SDK earns its popularity by making the hard parts of AI integration — streaming, provider abstraction, schema validation, and tool orchestration — into small, composable functions. The examples above cover the patterns that cover most real applications: streamed completions for responsiveness, useChat for conversational state, generateObject with Zod for reliable structured data, and tool calling with maxSteps for agentic behavior. The fastest way to internalize these patterns is to build one small feature end to end: pick a real problem in your own product, wire up a single streaming route handler with one tool or one schema, and deploy it. Working against the official documentation as you go will keep you current, since the SDK's API surface continues to evolve quickly.


Transparency Note: Links in this article are not affiliate links. I recommend these tools and platforms based on their relevance to the topic, and no compensation is received for including them.

Privacy Overview

This website uses cookies so that we can provide you with the best user experience possible. Cookie information is stored in your browser and performs functions such as recognising you when you return to our website and helping our team to understand which sections of the website you find most interesting and useful.

Necessary

Strictly Necessary Cookie should be enabled at all times so that we can save your preferences for cookie settings.

Analytics

This website uses Google Analytics to collect anonymous information such as the number of visitors to the site, and the most popular pages.

Keeping this cookie enabled helps us to improve our website.