Why I skipped the official Anthropic SDK for browser work, and the ~150 lines of TypeScript that replaced it
---
title: Building a streaming Claude client in the browser — without the SDK
published: true
canonical_url: https://ferhatatagun.com/blog/browser-only-claude-streaming
description: Why I skipped the official Anthropic SDK for browser work, and the ~150 lines of TypeScript that replaced it
tags: claude, anthropic, typescript, webdev
cover_image:
---
I wanted to call Claude from a browser. The Anthropic SDK said no — sort of.
When I tried `import Anthropic from "@anthropic-ai/sdk"` in a Next.js app, the bundler crashed. The error pointed at `node:fs/promises`, deep inside the package — an agent-toolset module that reads files from disk and obviously cannot run in a browser. It isn't optional code; it's pulled in by the SDK's main client entry.
So either I waited for a browser-clean entry point (eventually, maybe), or I talked to the Messages API directly. The endpoint is HTTP. The streaming format is Server-Sent Events. I'd done this for OpenAI before — how hard could it be?
Turns out: about 150 lines of TypeScript for a usable client, and the result is cleaner than the SDK for the kind of tool I was building. Here's what that took and why I'd recommend it for anything browser-side that touches the Claude API.
**TL;DR**
Browsers won't let you `fetch()` `https://api.anthropic.com` by default. Anthropic ships a flag to allow it: send `anthropic-dangerous-direct-browser-access: true` and CORS opens up. The header's name is a warning — keys typed into a browser are visible to anyone with devtools open. For a bring-your-own-key developer tool that's fine; for a production app shipping a server-side key, it isn't.
With the header in place, a minimal request looks like this:
await fetch("https://api.anthropic.com/v1/messages", {
method: "POST",
headers: {
"content-type": "application/json",
"x-api-key": apiKey,
"anthropic-version": "2023-06-01",
"anthropic-dangerous-direct-browser-access": "true",
},
body: JSON.stringify({
model,
max_tokens: 1024,
messages: [{ role: "user", content: "Hello." }],
stream: true,
}),
});`stream: true` gives back a Server-Sent Events stream. The response body is a `ReadableStream<Uint8Array>` — chunks of bytes you decode as text. Events are delimited by a blank line; each event is a couple of lines (`event: <type>` and `data: <json>`), and the meaningful payload lives in `data:`.
For a plain text response, th
// artículos relacionados
Twitter/X: 🚀 While you’re sleeping, the market is printing. I’m a futures trader, sharing my personal high…
Twitter/X: anthropic is literally crushing the new model benchmark https://t.co/Mhyy2SZtUQ
Twitter/X: EU regulator evaluating implications of Anthropic Mythos curbs after US directive https://t.co/FE…