Claude API React Frontend Integration Example
If you're searching for a Claude API React frontend integration example, you're probably trying to figure out how to wire a chat UI or AI feature into a React app without exposing your API key or fighting CORS errors. The short answer: you never call Claude directly from the browser. You build a small backend proxy, and React talks to that proxy over regular fetch or SSE.
This guide walks through a working setup — a minimal Node backend that forwards requests to Claude (or a Claude-compatible API), and a React component that sends messages, shows a typing indicator, and streams tokens as they arrive.
Why you can't call Claude directly from React
Anthropic's API (and most LLM APIs) require a secret key in the Authorization or x-api-key header. If you put that key in frontend JavaScript, it ships in the bundle and anyone can open dev tools and steal it. There's also no CORS allowance for browser-origin requests to api.anthropic.com, so a direct fetch call from React will fail anyway.
The correct architecture is:
React app → Your backend (Node/Express, Next.js API route, etc.) → Claude API
Your backend holds the secret key, does any request shaping (system prompts, tool definitions, rate limiting per user), and returns the result to the frontend. This is also where a tool like SubToAPI fits in: it gives you an HTTPS endpoint with its own scoped sub_live_... key that you can safely rotate per environment, plus usage metadata and streaming support, without changing how your React code talks to your backend.
Step 1: Build a minimal backend endpoint
Here's an Express route that accepts a list of messages and forwards them:
import express from "express";
const app = express();
app.use(express.json());
app.post("/api/chat", async (req, res) => {
const { messages } = req.body;
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-5",
max_tokens: 1024,
messages,
}),
});
const data = await response.json();
res.json(data);
});
app.listen(3001);
This is the whole backend surface your React app needs. If you want the exact request/response shape, the messages docs cover it in detail.
Step 2: The React chat component
import { useState } from "react";
export default function ChatBox() {
const [messages, setMessages] = useState([]);
const [input, setInput] = useState("");
const [loading, setLoading] = useState(false);
async function sendMessage() {
const userMessage = { role: "user", content: input };
const updated = [...messages, userMessage];
setMessages(updated);
setInput("");
setLoading(true);
const res = await fetch("/api/chat", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ messages: updated }),
});
const data = await res.json();
const assistantText = data.content?.[0]?.text ?? "No response";
setMessages([...updated, { role: "assistant", content: assistantText }]);
setLoading(false);
}
return (
<div>
<div>
{messages.map((m, i) => (
<p key={i}>
<strong>{m.role}:</strong> {m.content}
</p>
))}
{loading && <p>Thinking…</p>}
</div>
<input
value={input}
onChange={(e) => setInput(e.target.value)}
onKeyDown={(e) => e.key === "Enter" && sendMessage()}
/>
<button onClick={sendMessage}>Send</button>
</div>
);
}
That's a complete round trip: React calls your own /api/chat, your backend calls the Claude-compatible API, and the response renders in the UI. No key ever touches the browser.
Step 3: Add streaming for a better UX
Waiting for a full response before showing anything feels slow for longer answers. Streaming fixes that by sending tokens as they're generated. On the backend, request stream: true and pipe the server-sent events through to the client:
app.post("/api/chat/stream", async (req, res) => {
const upstream = 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-5",
max_tokens: 1024,
stream: true,
messages: req.body.messages,
}),
});
res.setHeader("Content-Type", "text/event-stream");
upstream.body.pipe(res);
});
On the React side, use EventSource or read the fetch response body as a stream and append chunks to state as they arrive. This is enough to build a token-by-token typing effect similar to Claude.ai. For the full event format and reconnection behavior, see the streaming docs.
Handling tool calls in the UI
If your React app needs Claude to trigger actions — looking up an order, calling a search API, updating a record — you define tools on the backend request and handle the tool_use block when it comes back. The frontend doesn't need to know about tool schemas; it just renders the final assistant text after your backend executes the tool and sends the result back to Claude in a follow-up call. Details on the request/response shape are in the tools docs.
Common mistakes to avoid
- Putting the API key in
.envfiles prefixed withREACT_APP_orNEXT_PUBLIC_. Anything with those prefixes gets bundled into client JavaScript and is publicly visible. - Skipping a proxy "for now." It's tempting during a prototype, but the key ends up in git history or a public deploy fast.
- Not handling rate limits or timeouts in the UI. Show a retry button or graceful error message when the upstream call fails.
- Re-sending the entire conversation history on every request without trimming — this gets expensive and eventually hits context limits.
If you'd rather not run and monitor your own proxy server, SubToAPI gives you a hosted HTTPS endpoint with per-application keys, usage metadata, and team seats, so your React app's backend only needs one small fetch call and no infrastructure to babysit. The quickstart guide shows the setup end to end, and pricing covers the Solo, Team, and Scale plans if you're evaluating for a production app.
questions
Can I call the Claude API directly from a React component without a backend? No. Browser CORS policy blocks it, and shipping your API key in frontend code exposes it to anyone viewing your bundle. Always route requests through a backend proxy you control.
How do I show streaming responses in React? Have your backend forward the API's server-sent events stream and consume it in React with EventSource or a ReadableStream reader, appending each chunk to component state as it arrives.
What's the fastest way to add Claude to an existing React app? Add one backend route that forwards to a messages endpoint, then call that route from a fetch-based React component. Using a service like SubToAPI removes the need to manage your own key rotation or usage tracking on top of that.