← the writing notes 9 min

Vercel AI SDK in Practice: Ship Smarter, Not Harder

After wiring AI into dozens of client projects, I've landed on Vercel AI SDK as the fastest path from "we want AI features" to production. Here's the honest breakdown of what it does and how to ship with it.

Handwritten code notes in a notebook on a wooden desk with soft overcast light and an espresso cup

A fintech client rang me in late 2023, proper panic in his voice, because his dev team had spent six weeks building a streaming chat interface on top of OpenAI's API and it was still broken on mobile Safari. Race conditions. Token chunking issues. The UI would freeze mid-response. Six weeks. I looked at their codebase and it was exactly what you'd expect: a hand-rolled ReadableStream implementation, a bespoke state management layer for the streaming tokens, retry logic copied from a three-year-old Stack Overflow answer. The thing was held together with tape.

I rewrote the core of it using the Vercel AI SDK in about two days. Streaming worked. Mobile Safari worked. The client stopped ringing.

That's not a pitch. That's just what happened. And it's why I want to walk through what this SDK actually does, where it genuinely saves you time, and where you'll still hit walls.

What the Vercel AI SDK Actually Is

People hear "Vercel AI SDK" and assume it's locked into Vercel infrastructure. It isn't. You can run it on any Node.js server, including your own VPS, Railway, Render, wherever. What it actually is: a TypeScript library that abstracts away the messiest parts of building LLM-powered features into a web app.

There are two main packages you'll reach for. ai is the core runtime. @ai-sdk/openai (or @ai-sdk/anthropic , @ai-sdk/google, etc.) are the provider adapters. The SDK uses a unified interface so switching from GPT-4o to Claude 3.5 Sonnet is genuinely a one-line change in most cases. I've tested this. It holds up.

The Core Primitives

Three things sit at the heart of the SDK:

  • streamText, streams a text response token by token, ideal for chat interfaces
  • generateText, waits for the full response, good for background jobs or when you don't need streaming UX
  • generateObject, returns structured JSON validated against a Zod schema, which is where things get properly interesting

streamText is what most people want first. It handles the ReadableStream plumbing, the encoding, and the chunking. That six-week fintech nightmare I mentioned? That's exactly what streamText would have solved on day one.

Setting It Up Without Losing Your Mind

Assuming you're on Next.js 14 or 15 with the App Router (which you probably are if you're reading this in 2025), the setup is genuinely quick.

  1. Install the packages: npm install ai @ai-sdk/openai
  2. Create a route handler at app/api/chat/route.ts
  3. Import streamText and your provider, pass in your model and messages, return the result as a StreamingTextResponse
  4. On the client, use the useChat hook from the ai/react package

That's it. You'll have a working streaming chat in under an hour if you know Next.js reasonably well. The useChat hook manages the message array, the loading state, the input field binding, and the streaming updates. You don't write any of that yourself.

What I'd add immediately to any production setup: a proper system prompt, rate limiting (I use Upstash for this, their Redis-backed rate limiter plays nicely with Edge Functions), and some kind of token usage logging. The SDK exposes usage data on the response object, so you can log prompt tokens and completion tokens to your database without any extra API calls.

generateObject Is the Feature You're Sleeping On

Honest opinion: streamText gets all the press but generateObject is the one that's changed how I architect AI features.

The idea is simple. You define a Zod schema, pass it to generateObject, and the SDK instructs the model to return JSON that conforms to it. You get back a typed object, not a string you have to parse and pray over.

Seahawk had a project last year for a property management company. They wanted to extract structured data from uploaded lease documents, tenant names, rent amounts, break clauses, renewal dates. The old approach would have been: prompt the model, get text back, write a regex parser, cry. With generateObject , we defined a LeaseSchema with Zod, and the model returned clean typed data on every run. We were pushing extracted leases into a Postgres table within a week.

The thing that trips people up: not all models support structured output equally well. GPT-4o and Claude 3.5 Sonnet handle it reliably. Some smaller or older models will hallucinate fields or ignore the schema entirely. Stick to the flagship models for this one, at least until you've validated your use case.

Tool Calling: Where the Real Power Lives

If generateObject is underused, tool calling is actively misunderstood. Most developers think "tool calling" means the AI can browse the internet or run code. Sometimes it does. But in practice, tools are just functions you define that the model can choose to invoke during a response.

How to Think About It

Say you're building a support bot for a SaaS product. The user asks "what's my current subscription plan?" The model can't know that. But you can define a getUserSubscription tool that takes a user ID and returns the plan data from your database. The model recognises the intent, calls the tool, gets the data back, and incorporates it into the response. The user just sees a coherent answer.

The SDK's tools parameter on streamText and generateText takes an object where each key is a tool name, and each value has a description (which the model uses to decide when to call it), a parameters schema (Zod again), and an execute function. The execute function runs server-side, safely, away from the client.

I've built multi-step agents this way. The SDK supports maxSteps so the model can chain tool calls, call one tool, use the result, call another tool, synthesise everything, respond. It's not magic. You still need to think carefully about your tool descriptions and your system prompt. But the wiring is all handled for you.

Middleware, Wrapping, and the Pipeline Model

One thing I didn't appreciate until I read deeper into the docs: the SDK has a middleware concept that lets you wrap model calls to add logging, caching, or custom behaviour without touching your actual feature code.

wrapLanguageModel takes a model and a middleware object. You can intercept requests before they hit the API, modify parameters, cache responses. I used this on a content generation tool we built for a publisher, their use case had a lot of repeated prompts (same article summary being requested multiple times a day), and we cached responses in Redis using the middleware layer. Cost dropped by about 35% in the first month.

This is the kind of thing you'd normally bolt on awkwardly around the outside of your API calls. Having it as a first-class concept in the SDK means it's composable and testable.

What It Doesn't Do (Be Honest With Yourself)

Right. Let me save you some pain.

The SDK does not handle memory or long-term conversation history for you. useChat maintains the message array in client state, but the moment the user refreshes, that's gone. If you need persistent conversations, you're building that yourself. Postgres or MongoDB for message storage, fetch on mount to rehydrate the chat, none of that is provided. This is correct behaviour, actually. The SDK shouldn't own your data layer. But newcomers often expect it to.

It also doesn't handle multimodal inputs natively in a way that abstracts away all the complexity. You can pass image URLs in the messages array and models like GPT-4o will handle them fine, but building a proper image upload flow with preview, compression, and storage is still your job. I use Uploadthing for this when I'm on Next.js, takes about 30 minutes to wire up.

RAG (retrieval-augmented generation) is also not included. There's no built-in vector search, no embedding pipeline, no chunking logic. For that you're reaching for something like pgvector on Postgres or a dedicated service like Pinecone. The SDK handles the LLM call. The rest of the retrieval architecture is yours to build.

Deploying It: Vercel vs Everywhere Else

Yes, the SDK works best on Vercel. The StreamingTextResponse works with Vercel's Edge Runtime out of the box, and cold starts on Edge Functions are much lower than on serverless Node.js functions. If your chat responses feel sluggish, the latency until the first token matters a lot, and Edge cuts that down.

But I've shipped it on Railway (Node.js, not Edge) and it works fine. You just use Response with proper streaming headers instead of the Vercel-specific helpers, and the SDK docs cover this. Don't let "Vercel AI SDK" make you think you're locked in.

One thing I'd genuinely recommend: keep your AI route handlers thin. Don't do database queries, authentication checks, and LLM calls all in one function. Middleware (Next.js middleware, not the SDK middleware) should handle auth before the request even reaches your AI route. Keeps things fast, keeps things debuggable.

FAQ

Is the Vercel AI SDK free to use?

The SDK itself is open source and free. You're paying for the underlying model APIs, OpenAI, Anthropic, Google, whoever you're calling. Those costs are yours to manage. The SDK doesn't mark anything up.

Can I use it with models other than OpenAI?

Yes, and this is one of its genuine strengths. There are official adapters for Anthropic, Google (Gemini), Mistral, Groq, Cohere, and more. The community has also built adapters for Ollama if you want to run local models. The unified interface means your feature code doesn't change when you swap providers.

Does it work with React Server Components?

Partially. generateText and generateObject can run in Server Components without issues, they're just async functions. streamText with the useChat hook requires client-side state, so that part lives in a Client Component. This is standard Next.js architecture and shouldn't cause you any grief if you understand the boundary.

How do I handle errors when the API goes down?

The SDK throws typed errors you can catch and handle. In practice I wrap my LLM calls in a try/catch and return a graceful fallback message rather than letting the stream error bubble up to the UI as a broken partial response. The SDK docs on error handling cover the error types in detail and are worth reading before you go to production.

What's the token limit situation?

The SDK doesn't manage context window limits for you. If you're building a long-running conversation and you don't trim the message history, you'll hit the model's context window and get an error. A simple fix: keep the last N messages (I usually do 20) plus a pinned system prompt. Good enough for most chat applications.

---

Look, the Vercel AI SDK isn't a miracle. It's a well-designed abstraction over some genuinely tedious plumbing. It handles streaming correctly, it gives you typed structured outputs, it makes tool calling approachable, and it works across providers. For 80% of AI feature work I take on, it's the right starting point.

The fintech client, by the way, went live with the rebuilt chat two weeks after I rewrote it. No issues on mobile Safari. No race conditions. Six weeks became two days. Sometimes the right abstraction is worth a lot.

Need this done, not just read?

start a project book 30 minutes