Claude API React Frontend Integration Guide
Claude API React Frontend Integration
If you're building a React app and want to add Claude-powered chat, search, or content generation, the core challenge isn't calling the API — it's calling it safely from a browser environment while still getting a responsive, streaming UI. This guide covers both: the architecture pattern you need and the actual React code for rendering a Claude response as it streams in.
The short answer: you should never call api.anthropic.com directly from client-side React code, because that exposes your API key to anyone who opens dev tools. Instead, you need a backend proxy (your own server or a hosted API layer) that holds the key, and your React app talks to that proxy over HTTPS. Everything below assumes that setup.
Why You Can't Call Claude Directly from the Browser
Anthropic's API expects an x-api-key header on every request. If you put that key in your React bundle — even in an environment variable prefixed with REACT_APP_ or VITE_ — it ships to every visitor's browser as plain text in the compiled JS. Anyone can extract it and run up your bill.
The standard fix is a thin backend layer:
- React sends a request to your own endpoint (e.g.
/api/chat). - Your server attaches the real API key and forwards the request to Claude.
- The server streams the response back to React.
You can build this yourself with a few lines of Express or a serverless function, or use a hosted proxy like SubToAPI, which gives you an application-scoped key (sub_live_...) you can call from your backend without managing Anthropic credentials, streaming infrastructure, or rate-limit handling yourself.
Backend Proxy Example (Node/Express)
app.post("/api/chat", async (req, res) => {
const response = await fetch("https://api.subtoapi.app/v1/messages", {
method: "POST",
headers: {
"Authorization": `Bearer ${process.env.SUBTOAPI_KEY}`,
"Content-Type": "application/json"
},
body: JSON.stringify({
model: "claude-sonnet-4",
max_tokens: 1024,
stream: true,
messages: req.body.messages
})
});
res.setHeader("Content-Type", "text/event-stream");
response.body.pipe(res);
});
This keeps your key server-side and passes the stream through unchanged. See /docs/messages and /docs/streaming for the full request/response shape.
React Component: Streaming Chat UI
Once you have a proxy endpoint, the React side is about handling a streamed response and updating state incrementally so the text appears token-by-token instead of all at once.
import { useState } from "react";
function ChatBox() {
const [input, setInput] = useState("");
const [reply, setReply] = useState("");
const [loading, setLoading] = useState(false);
async function sendMessage() {
setLoading(true);
setReply("");
const res = await fetch("/api/chat", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
messages: [{ role: "user", content: input }]
})
});
const reader = res.body.getReader();
const decoder = new TextDecoder();
while (true) {
const { done, value } = await reader.read();
if (done) break;
const chunk = decoder.decode(value);
const lines = chunk.split("\n").filter(l => l.startsWith("data:"));
for (const line of lines) {
const data = line.replace("data: ", "");
if (data === "[DONE]") continue;
try {
const parsed = JSON.parse(data);
if (parsed.delta?.text) {
setReply(prev => prev + parsed.delta.text);
}
} catch {
// ignore malformed chunk boundaries
}
}
}
setLoading(false);
}
return (
<div>
<textarea value={input} onChange={e => setInput(e.target.value)} />
<button onClick={sendMessage} disabled={loading}>
{loading ? "Thinking..." : "Send"}
</button>
<p>{reply}</p>
</div>
);
}
export default ChatBox;
A few things worth noting about this pattern:
- Incremental rendering: appending to
replyon every chunk is what makes the UI feel live. Don't wait for the full response before updating state. - Malformed chunk handling: SSE data can arrive split across network packets, so wrap
JSON.parsein a try/catch rather than crashing the render loop. - Loading state: disable the send button while streaming to avoid duplicate in-flight requests, which is a common source of race conditions in chat UIs.
Handling Tool Use and Structured Output in React
If your Claude integration uses tool calling — for example, letting Claude query your database or call an internal API — the React layer doesn't need to know about tool execution at all. That logic stays on the backend: your proxy receives the tool-use request from Claude, executes the function, sends the result back, and only forwards the final text response (or a structured JSON payload) to React. This keeps your frontend simple and avoids exposing internal API contracts to the browser. Details on request format are in /docs/tools.
Error Handling and Rate Limits
Two failure modes matter most in production React apps:
- Network drops mid-stream: wrap the
fetch/readerloop in a try/catch and show a "connection lost, retry" state rather than a blank screen. - Rate limits or quota errors: your backend should return a distinct status code (e.g. 429) that React can catch and display as a friendly message, not a generic error.
If you're using a managed proxy, usage metadata (tokens consumed, request counts) is typically available per API key, which makes it easier to debug which frontend feature is driving cost without instrumenting this yourself.
Getting Set Up Quickly
If you don't want to build and maintain the proxy layer, SubToAPI turns your existing Claude access into an HTTPS API with per-application keys, built-in streaming, and usage dashboards — so your React app just calls a normal REST endpoint. Plans start at €9/month for solo developers, with team seats at €19 and €49 for larger projects. You can try it with a free trial at /signup, and the /pricing page has the full breakdown.
FAQ
Can I call the Claude API directly from a React component without a backend? Not safely. Doing so exposes your API key in the client bundle. Always proxy requests through a server you control, whether that's your own backend or a hosted API layer.
How do I show streaming text in React without a UI library? Use the Fetch API's ReadableStream reader to read chunks, decode them, and append the parsed text to component state on each chunk — as shown in the example above.
Does tool use change how I structure my React code? No. Tool execution happens on your backend proxy; React only receives the final response, so your components stay focused on rendering, not orchestration.