Running the Anthropic SDK on Edge Runtimes
The official @anthropic-ai/sdk package is built on fetch, so it runs unmodified on edge runtimes like Vercel Edge Functions, Cloudflare Workers, and Deno Deploy, not just in Node.js. This page covers how to configure the client and stream responses correctly on each platform.
Summary
Edge runtimes are lightweight JavaScript environments that run close to the request, often without the full Node.js API surface. They don't have fs, net, or many other Node built-ins available.
@anthropic-ai/sdk avoids that problem because its request path is built on the standard fetch API, ReadableStream, and AbortController, all of which are available in every modern edge runtime.
The main differences between edge and Node.js deployments are how you read the API key (environment variable access differs per platform) and how you return a streamed response (edge handlers typically return a Response object directly instead of writing to a Node.js http.ServerResponse).
This page walks through a Vercel Edge Function (via a Next.js route handler), a Cloudflare Worker, and notes on Deno Deploy, each configuring the client and streaming a reply back to the caller.
Timeouts and cancellation work the same way on edge as they do in Node.js: pass an AbortSignal to the request, or rely on the SDK's own timeout setting, but remember the platform itself also imposes its own execution time limit on top of whatever you configure.
Recipe
Quick-reference recipe card, copy-paste ready.
// Works identically on Node.js, Vercel Edge, Cloudflare Workers, and Deno Deploy
import Anthropic from "@anthropic-ai/sdk";
const anthropic = new Anthropic({
apiKey: process.env.ANTHROPIC_API_KEY,
});
const message = await anthropic.messages.create({
model: "claude-sonnet-5",
max_tokens: 1024,
messages: [{ role: "user", content: "Explain edge runtimes in one paragraph." }],
});
console.log(message.content);When to reach for this:
- Serving low-latency Claude responses from a location close to the user (Vercel Edge, Cloudflare Workers both run at the network edge).
- Deploying to a platform that doesn't support the full Node.js runtime (Cloudflare Workers, Deno Deploy).
- Streaming a chat response directly back to the browser without buffering the full reply first.
- Keeping a serverless function's cold-start time low; edge runtimes generally start faster than Node.js functions.
Working Example
Vercel Edge Function (Next.js route handler)
// app/api/chat/route.ts
import Anthropic from "@anthropic-ai/sdk";
export const runtime = "edge";
const anthropic = new Anthropic({
apiKey: process.env.ANTHROPIC_API_KEY,
});
export async function POST(req: Request) {
const { prompt } = await req.json();
const stream = await anthropic.messages.create({
model: "claude-sonnet-5",
max_tokens: 1024,
messages: [{ role: "user", content: prompt }],
stream: true,
});
const encoder = new TextEncoder();
const body = new ReadableStream({
async start(controller) {
for await (const event of stream) {
if (event.type === "content_block_delta" && event.delta.type === "text_delta") {
controller.enqueue(encoder.encode(event.delta.text));
}
}
controller.close();
},
});
return new Response(body, {
headers: { "Content-Type": "text/plain; charset=utf-8" },
});
}What this demonstrates:
- Setting
export const runtime = "edge"opts the Next.js route handler into the Vercel Edge runtime. - The API key is read from
process.env.ANTHROPIC_API_KEY, which Vercel injects into edge functions the same way it does for Node.js functions, as long as the environment variable is set in the project's settings. - The SDK's async iterator (
for await (const event of stream)) is wrapped in aReadableStreamso the handler can return a streamingResponseinstead of waiting for the full message. - Only
text_deltacontent block deltas are forwarded to the client; other event types (message start, content block start/stop, message stop) are ignored for this simple text-streaming case.
Cloudflare Worker
// src/worker.ts
import Anthropic from "@anthropic-ai/sdk";
export interface Env {
ANTHROPIC_API_KEY: string;
}
export default {
async fetch(request: Request, env: Env): Promise<Response> {
if (request.method !== "POST") {
return new Response("Use POST", { status: 405 });
}
const anthropic = new Anthropic({
apiKey: env.ANTHROPIC_API_KEY,
});
const { prompt } = await request.json<{ prompt: string }>();
const stream = await anthropic.messages.create({
model: "claude-sonnet-5",
max_tokens: 1024,
messages: [{ role: "user", content: prompt }],
stream: true,
});
const encoder = new TextEncoder();
const body = new ReadableStream({
async start(controller) {
for await (const event of stream) {
if (event.type === "content_block_delta" && event.delta.type === "text_delta") {
controller.enqueue(encoder.encode(event.delta.text));
}
}
controller.close();
},
});
return new Response(body, {
headers: { "Content-Type": "text/plain; charset=utf-8" },
});
},
};What this demonstrates:
- Cloudflare Workers pass configuration and secrets through a typed
envobject argument tofetch, not throughprocess.env. TheANTHROPIC_API_KEYis declared on anEnvinterface and read asenv.ANTHROPIC_API_KEY. - The secret itself is set outside the code, with
wrangler secret put ANTHROPIC_API_KEY, so it never lives in the repository or thewrangler.tomlfile. - The client is instantiated inside the
fetchhandler (not at module scope) so it always picks up theenvbindings for the current request. - The streaming pattern is identical to the Vercel Edge example because both platforms implement the same
ReadableStreamandResponseweb standards.
Canceling the Upstream Call on Client Disconnect
Both examples above can forward the incoming request's own AbortSignal to the SDK call, so a client disconnect cancels the upstream request to Claude instead of letting it run to completion:
const message = await anthropic.messages.create(
{
model: "claude-sonnet-5",
max_tokens: 1024,
messages: [{ role: "user", content: prompt }],
},
{ signal: request.signal },
);- Wiring
AbortControllerto the incoming request'ssignalmeans canceling the client (closing the browser tab, navigating away) also cancels the upstream call to Claude, instead of letting it run to completion and burning tokens for a response nobody will read. request.signalis available on theRequestobject in both Vercel Edge Functions and Cloudflare Workers because both implement the standard Fetch API.
Deep Dive
How It Works
@anthropic-ai/sdksends requests with the globalfetchfunction rather than a Node-specific HTTP client, so it has no dependency on Node built-ins likehttp,https, ornetfor its core request path.- Streaming responses from the Messages API arrive as server-sent events, which the SDK parses and exposes as an async iterable of typed message events (
for await (const event of stream)). - Edge runtimes execute your handler in a lightweight, often V8-isolate-based sandbox, not a full Node.js process, which is why Node-only APIs (
fs,child_process, native modules) aren't available and why a fetch-based SDK is the deciding factor for compatibility. - Because the request/response cycle in an edge handler is built on the same
Request/Response/ReadableStreamprimitives the SDK uses internally, you can pipe the SDK's stream straight into the function's return value without an adapter layer.
API Key Handling by Platform
| Platform | Where the key lives | How you read it |
|---|---|---|
| Vercel Edge Function | Project environment variables (Vercel dashboard or .env.local for local dev) | process.env.ANTHROPIC_API_KEY |
| Cloudflare Worker | Secret set via wrangler secret put ANTHROPIC_API_KEY | env.ANTHROPIC_API_KEY (passed as an argument to fetch) |
| Deno Deploy | Project environment variable set in the Deno Deploy dashboard (or .env for local dev with deno run --env) | Deno.env.get("ANTHROPIC_API_KEY") |
Deno Deploy Notes
// main.ts (Deno Deploy)
import Anthropic from "npm:@anthropic-ai/sdk";
const anthropic = new Anthropic({
apiKey: Deno.env.get("ANTHROPIC_API_KEY"),
});
Deno.serve(async (request: Request) => {
const { prompt } = await request.json();
const message = await anthropic.messages.create({
model: "claude-sonnet-5",
max_tokens: 1024,
messages: [{ role: "user", content: prompt }],
});
return Response.json(message);
});- Deno Deploy imports npm packages with the
npm:specifier, soimport Anthropic from "npm:@anthropic-ai/sdk"works without apackage.jsonor a build step. - Secrets are read with
Deno.env.get(...), notprocess.env; Deno does expose aprocessshim for compatibility, butDeno.envis the native, recommended path. Deno.serveis the built-in HTTP entry point on Deno Deploy and, like the other two platforms, takes and returns standardRequest/Responseobjects.
TypeScript Notes
// Narrow the streaming event union before reading delta.text
for await (const event of stream) {
if (event.type === "content_block_delta" && event.delta.type === "text_delta") {
// event.delta.text is safely typed as string here
process.stdout.write(event.delta.text);
}
}- The SDK ships its own TypeScript types, so
message.content, streaming events, and error classes are all typed without extra@typespackages. - Streaming events are a discriminated union on
event.type; narrowing with anifcheck (as above) is what lets TypeScript knowevent.delta.textexists, rather than reaching for a type assertion. - Avoid
as anycasts on the client'sapiKeyoption. Ifprocess.env.ANTHROPIC_API_KEY(orenv.ANTHROPIC_API_KEY) isundefinedat runtime because the variable wasn't set, let the SDK throw its normal authentication error rather than silencing the type checker.
Parameters & Return Values
| Parameter | Type | Description |
|---|---|---|
apiKey | string | Anthropic API key. Required unless set via ANTHROPIC_API_KEY in an environment the SDK reads automatically (Node.js only; edge runtimes must pass it explicitly). |
timeout | number | Milliseconds before the SDK aborts the request client-side. Independent of the edge platform's own execution limit. |
maxRetries | number | Number of automatic retries on transient errors (rate limits, network failures, 5xx responses). Defaults to 2. |
signal | AbortSignal | Passed per-request to cancel an in-flight call, e.g. anthropic.messages.create(params, { signal }). |
Gotchas
- Reading
process.envon Cloudflare Workers. Cloudflare Workers do not populateprocess.envwith secrets by default; they pass configuration through theenvargument on thefetchhandler. Fix: readenv.ANTHROPIC_API_KEYinside the handler, notprocess.env.ANTHROPIC_API_KEYat module scope. - Instantiating the client at module scope on Cloudflare Workers. If you build the
Anthropicclient outside thefetchhandler,envisn't available yet when the module loads. Fix: construct the client inside the handler function whereenvis passed in as an argument. - Assuming edge runtimes have no timeout. Vercel Edge Functions, Cloudflare Workers, and Deno Deploy each enforce their own maximum execution or CPU time per request, on top of anything you configure on the SDK client. Fix: check the current limits for your plan on each platform and design long-running generations around streaming (return partial output as it arrives) rather than waiting for one large non-streamed response.
- Forgetting to set
stream: trueand then trying to iterate the result. Callinganthropic.messages.create()withoutstream: truereturns a single resolvedMessageobject, not an async iterable; attempting afor awaitloop over it fails. Fix: passstream: truein the request parameters whenever you intend to consume the response as a stream. - Not narrowing the streaming event union before reading
delta.text. Streaming events include several types (message_start,content_block_start,content_block_delta,content_block_stop,message_stop, and more), and onlycontent_block_deltaevents withdelta.type === "text_delta"carry.text. Fix: check bothevent.typeandevent.delta.typebefore readingevent.delta.text. - Leaving the API key in client-side code. Because edge functions run server-side, it's tempting to think any code in the same repo is safe, but a key hardcoded into a route handler that also ships a client bundle (or copy-pasted into a Cloudflare Worker committed to source control) can leak. Fix: always load the key from an environment variable or secret store, never hardcode it, and add
.env*files to.gitignore. - Assuming
AbortControllerneeds a Node-specific import.AbortControllerandAbortSignalare global in Node.js 18+, all major browsers, and every edge runtime covered here, so no import or polyfill is needed. Fix: just use the globalAbortController, or forwardrequest.signalfrom an incomingRequestwhen you want client disconnects to cancel the upstream Claude call.
Alternatives
| Alternative | Use When | Don't Use When |
|---|---|---|
| Node.js serverless function (Vercel Node runtime, AWS Lambda) | You need full Node.js APIs (filesystem access, certain native npm packages) alongside the SDK call. | Latency to the nearest edge location matters more than Node compatibility, or the platform bills edge and Node functions differently and edge is cheaper for your traffic. |
| A dedicated backend server (Express, Fastify) calling the SDK | You already run a long-lived server process and want centralized logging, connection pooling, or non-HTTP integrations. | You want per-request scaling to zero and low cold-start latency, which edge and serverless functions provide more naturally than a always-on server. |
Calling the Messages API with raw fetch instead of the SDK | You need a minimal bundle with zero dependencies, or you're in a runtime the SDK hasn't been verified on. | You want typed request/response shapes, built-in retry and timeout handling, and streaming event parsing, which the SDK provides out of the box. |
FAQs
Does the Anthropic TypeScript SDK need any special edge-runtime build or package?
No. Install the same @anthropic-ai/sdk package you'd use in Node.js. It works on edge runtimes because its request path is built on fetch, ReadableStream, and AbortController, all of which are standard Web APIs available in edge environments.
Why doesn't `process.env.ANTHROPIC_API_KEY` work in my Cloudflare Worker?
- Cloudflare Workers don't automatically populate
process.envwith secrets the way Node.js does. - Secrets and variables are passed as the
envargument to thefetchhandler instead. - Read the key as
env.ANTHROPIC_API_KEYinside the handler, and set it withwrangler secret put ANTHROPIC_API_KEY.
Can I stream a Claude response directly back to the browser from an edge function?
Yes. Wrap the SDK's async iterator in a ReadableStream, enqueue each text delta as it arrives, and return that stream as the body of a standard Response object. Both Vercel Edge Functions and Cloudflare Workers accept a ReadableStream as a Response body natively.
How do I set the API key for a Deno Deploy project?
Set it as an environment variable in the Deno Deploy project dashboard (or in a local .env file for development), then read it with Deno.env.get("ANTHROPIC_API_KEY") rather than process.env.
Do I need to install anything differently to import the SDK in Deno?
No separate install step is required. Use the npm: specifier to import directly, for example import Anthropic from "npm:@anthropic-ai/sdk", and Deno resolves the npm package at runtime.
Will `maxRetries` and `timeout` still work on edge runtimes?
Yes, both options work the same way as in Node.js since they're implemented on top of fetch and standard timers. Keep in mind the edge platform also enforces its own maximum execution time independently, so a generous SDK timeout doesn't override the platform's limit.
What happens if I forget to set `stream: true`?
anthropic.messages.create() returns a single resolved Message object instead of an async iterable. Trying to run a for await loop over a non-streamed result will fail, so make sure stream: true is set whenever you intend to consume the response incrementally.
How do I cancel an in-flight Claude request if the user navigates away?
Forward the incoming request's AbortSignal to the SDK call, for example anthropic.messages.create(params, { signal: request.signal }). When the client disconnects, the platform aborts the signal and the SDK cancels the upstream request to Claude.
Which streaming event type actually contains the text I want to display?
content_block_delta events where event.delta.type === "text_delta" carry the incremental text in event.delta.text. Other event types like message_start, content_block_start, and message_stop mark structural boundaries in the stream and don't carry text content.
Is it safe to hardcode my API key directly in a Cloudflare Worker's source file for a quick test?
No. Even for testing, load the key from an environment variable or Cloudflare secret. A key committed to source control (even temporarily) can end up in version history or a public repository, and Workers secrets set with wrangler secret put are the supported way to keep it out of the codebase entirely.
Does the same code run unchanged across Vercel Edge, Cloudflare Workers, and Deno Deploy?
The SDK usage (client construction, messages.create, streaming) is the same across all three. What differs is how each platform exposes environment variables/secrets (process.env on Vercel, an env argument on Cloudflare, Deno.env.get on Deno) and, for Cloudflare, how the fetch handler signature is shaped.
Do I need a polyfill for `AbortController` on any of these platforms?
No. AbortController and AbortSignal are globally available in Node.js 18+, and in Vercel Edge Functions, Cloudflare Workers, and Deno Deploy, since all of them implement the standard Fetch API surface.
Related
- TypeScript/JavaScript SDK Basics - installing the SDK and sending your first typed Messages API call.
- Async Iterators for Streaming in the TypeScript SDK - a closer look at consuming streamed message deltas.
- Retry, Timeout, and AbortController Patterns in the TypeScript SDK - tuning backoff and cancellation in more depth.
- Building a Streaming Chat Endpoint with Next.js and the TypeScript SDK - a full chat endpoint built on the streaming pattern shown here.
- The Anthropic TypeScript SDK Mental Model - how the SDK's request/response and streaming model fits together.
Stack versions: Written against the Claude model lineup current as of ~June 2026 - Claude Fable 5, Claude Opus 4.8, Claude Sonnet 5 (the default), and Claude Haiku 4.5 - and the official
@anthropic-ai/sdkTypeScript SDK (latest release). Model names, SDK versions, and pricing move quickly - verify current specifics at platform.claude.com/docs before relying on them.