n4nAI

Deploy a Vercel AI SDK app to Cloudflare Workers

A practical walkthrough of vercel ai sdk cloudflare workers deployment: scaffold an edge AI app, configure the provider, ship to Workers, and verify.

n4n Team5 min read1,004 words

Audio narration

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

Shipping a Vercel AI SDK app to the edge used to mean wrestling with Node shims and broken streams. This guide covers a clean vercel ai sdk cloudflare workers deployment path: we’ll stand up a streaming chat endpoint on Cloudflare Workers using the AI SDK’s provider-agnostic interface and the OpenAI compatibility layer, then push it live with Wrangler.

Step 1: Scaffold a Minimal Worker Project

Start with an empty directory and a package.json that targets the edge. You do not need Next.js, a Vercel build step, or a bundler pipeline; Workers executes compiled TypeScript directly via Wrangler’s esbuild stage. Keeping the project framework-free removes an entire class of “works locally, dies on deploy” bugs.

mkdir edge-ai-worker && cd edge-ai-worker
npm init -y
npm install ai @ai-sdk/openai
npm install -D wrangler typescript @cloudflare/workers-types

Create a tsconfig.json that strips types without pulling in Node built-ins. The AI SDK is written against Web Standards, so you only need the DOM and ES2022 libs.

{
  "compilerOptions": {
    "target": "ES2022",
    "module": "ESNext",
    "moduleResolution": "Bundler",
    "lib": ["ES2022", "DOM"],
    "types": ["@cloudflare/workers-types"],
    "strict": true,
    "noEmit": true
  }
}

Do not add @types/node. If you accidentally import process or fs, the Worker will fail at runtime, not at compile time, because those globals are undefined on the edge. The only runtime dependencies are ai and @ai-sdk/openai.

Step 2: Configure Wrangler for the Edge Runtime

Wrangler is the deploy CLI and local dev server. A minimal wrangler.toml tells Cloudflare to treat your script as an ES module and sets a recent compatibility date. Avoid nodejs_compat unless a specific provider SDK demands it; the AI SDK uses Web Streams and fetch natively, and enabling Node compat adds startup overhead.

name = "edge-ai-worker"
main = "src/index.ts"
compatibility_date = "2024-09-23"
compatibility_flags = ["nodejs_compat"]

[vars]
MODEL = "gpt-4o-mini"

Store the API key as a secret, never as a plaintext var:

wrangler secret put LLM_API_KEY
# paste your provider key when prompted

If you point at a gateway instead of a single vendor, the key is the gateway key. For example, n4n.ai exposes one OpenAI-compatible endpoint covering 240+ models with automatic fallback when a provider is degraded; set LLM_BASE_URL to that endpoint and use the same secret pattern. This keeps your Worker code identical regardless of which backend model is live.

For local development, create a .dev.vars file (git-ignored) with the same keys:

echo "LLM_API_KEY=sk-test" >> .dev.vars
echo "LLM_BASE_URL=https://gateway.n4n.ai/v1" >> .dev.vars

Wrangler loads .dev.vars automatically when you run wrangler dev.

Step 3: Implement the Chat Endpoint with the Vercel AI SDK

The Worker entry parses a JSON body with messages, forwards it to streamText, and returns a standard Response streaming text. The AI SDK’s toTextStreamResponse() builds a ReadableStream that Workers can return without extra adapters. No node-fetch, no express, no body-parser.

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

interface Env {
  LLM_API_KEY: string;
  LLM_BASE_URL?: string;
  MODEL: string;
}

export default {
  async fetch(req: Request, env: Env): Promise<Response> {
    if (req.method !== 'POST') {
      return new Response('Send POST with {messages:[{role,content}]}', { status: 405 });
    }

    const { messages } = await req.json<{ messages: { role: string; content: string }[] }>();

    const provider = createOpenAI({
      apiKey: env.LLM_API_KEY,
      baseURL: env.LLM_BASE_URL ?? 'https://api.openai.com/v1',
    });

    const result = streamText({
      model: provider(env.MODEL),
      messages,
    });

    return result.toTextStreamResponse();
  },
};

A few notes from production experience:

  • req.json<T>() is typed but unchecked. Validate messages before passing to the model; a malformed role crashes the stream mid-flight and the client gets a truncated response.
  • Set temperature or maxTokens on streamText if you want deterministic latency. Defaults are fine for chat but unbounded output will hold the connection open.
  • If you use a gateway that honors client routing directives, you can pass headers in createOpenAI: headers: { 'x-routing': 'cost-optimize' }. The n4n.ai gateway forwards provider cache-control hints, so your streamText call benefits from upstream prompt caching without extra code.
  • Return the response immediately. Do not await result.text() in the Worker; that defeats streaming and doubles your bill.

If you need CORS (e.g., the frontend lives on a different domain), add headers to the response:

return new Response(result.toTextStreamResponse().body, {
  headers: {
    'content-type': 'text/plain; charset=utf-8',
    'access-control-allow-origin': '*',
  },
});

Step 4: Stream from the Browser

A Worker alone is an API. To verify the vercel ai sdk cloudflare workers deployment end to end, add a static HTML page that posts to the endpoint and reads the stream. Cloudflare serves it from the same Worker by checking the path.

Extend the fetch handler:

const url = new URL(req.url);
if (req.method === 'GET' && url.pathname === '/') {
  return new Response(html, { headers: { 'content-type': 'text/html' } });
}

Where html is a string with a <textarea> and a button that calls:

const res = await fetch('/', {
  method: 'POST',
  headers: { 'content-type': 'application/json' },
  body: JSON.stringify({ messages: [{ role: 'user', content: input.value }] }),
});
const reader = res.body.getReader();
const decoder = new TextDecoder();
while (true) {
  const { done, value } = await reader.read();
  if (done) break;
  output.textContent += decoder.decode(value);
}

This avoids a build step and proves streaming works through Cloudflare’s edge. If you prefer the React useChat hook from @ai-sdk/react, you can point its api at this Worker, but that requires a separate bundler; for a how-to focused on deployment, vanilla JS is clearer.

Step 5: Deploy and Verify the vercel ai sdk cloudflare workers deployment

Run the local server first to catch type errors and secret binding issues:

wrangler dev
# open http://localhost:8787 and send a message

Once local streaming works, publish:

wrangler deploy

Expect a *.workers.dev URL. Verify with curl using -N to disable buffering:

curl -N -X POST https://edge-ai-worker.<sub>.workers.dev/ \
  -H 'content-type: application/json' \
  -d '{"messages":[{"role":"user","content":"Say hello in 5 words"}]}'

You should see token chunks arrive line by line. If you get a 405, you hit the GET path; use the correct method. If the stream closes immediately with an error, run wrangler tail to see the exception—usually a missing LLM_API_KEY or an invalid model name. For a final check, load the HTML page from your .workers.dev domain and send a message; the response should append live without a full-page reload.

Caveats When Running AI SDK on Workers

Cloudflare Workers bill for wall-clock time, not just CPU. A streaming response that stays open for 20 seconds while the model thinks will cost you that duration regardless of how little compute you use. Set maxTokens and trim conversation history before sending to bound cost.

The AI SDK’s streamText buffers internally for backpressure; on Workers that works, but if you pipe through additional transforms, remember that ReadableStream cancellation is cooperative. If the client disconnects, the Worker may keep running until the next chunk flush. Handle req.signal if you need early termination.

Do not import node:fs or node:crypto unless you enable nodejs_compat. The Web Crypto API (crypto.subtle) is available globally and is faster on the edge. Likewise, avoid Buffer; use Uint8Array and TextEncoder.

Model availability depends on your provider. If you rely on a single vendor, a rate limit returns a 429 inside the stream and the client sees a broken response. Using a gateway with automatic fallback avoids that class of failure without changing the AI SDK call—the same createOpenAI instance just points at a different base URL.

Cold starts on Workers are typically sub-millisecond for pure JavaScript, but the first TLS handshake to the LLM provider adds latency. Warm it with a scheduled request if you have strict p99 requirements.

What You Get

After these steps you have a TypeScript Worker that serves a streaming LLM endpoint built on the Vercel AI SDK, deployed to Cloudflare’s edge network. The vercel ai sdk cloudflare workers deployment pattern here scales to dozens of regions without a single server, and the same code runs behind a custom domain with a few wrangler flags and a routes entry in wrangler.toml. You can extend it with tool calls, structured output, or RAG retrieval using the same streamText interface—none of that requires leaving the edge runtime.

Tagsvercel-ai-sdkcloudflare-workersedgedeployment

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 on edge & serverless runtimes posts →