How to Integrate Claude Into an Existing React App
The short answer
You cannot call Claude's API directly from a React component running in the browser. Anthropic's API requires a secret API key in the request headers, and if you put that key in your frontend code, it ships to every visitor's browser and shows up in the network tab in plain text. Within hours someone will scrape it and run up your bill.
The correct integration pattern is always: React app → your backend (or a hosted API proxy) → Claude. The backend holds the secret key, the React app only talks to an endpoint you control over HTTPS. This article walks through that pattern end to end — adding a chat endpoint, calling it from React, streaming tokens into the UI, and handling tool calls — whether you build the backend yourself or use a managed layer like SubToAPI to skip that step.
Step 1: Add a server-side endpoint
If your React app already has a backend (Express, Next.js API routes, Fastify, whatever), add one route that accepts messages from the client and forwards them to Claude.
// server/routes/chat.js (Express example)
import Anthropic from "@anthropic-ai/sdk";
const anthropic = new Anthropic({ apiKey: process.env.CLAUDE_API_KEY });
app.post("/api/chat", async (req, res) => {
const { messages } = req.body;
const response = await anthropic.messages.create({
model: "claude-sonnet-4-5",
max_tokens: 1024,
messages,
});
res.json(response);
});
This works fine for prototypes. The catch: you now own key rotation, rate limiting, retries on 429s, logging, and (if more than one person on your team needs access) a way to issue separate credentials per environment or feature. For a side project that's a day of work. For a product with real users, it's ongoing maintenance.
If you'd rather not build that layer, SubToAPI gives you the same POST /v1/messages shape behind an HTTPS endpoint with your own sub_live_... key, so the "add a backend endpoint" step becomes pointing your existing server (or even a lightweight serverless function) at https://api.subtoapi.app/v1/messages instead of writing the proxy logic yourself. Either way, the React app below doesn't change.
Step 2: Call the endpoint from React
Your component never sees the Claude key. It calls your own /api/chat route (or whatever you name it), which does the forwarding.
function ChatBox() {
const [messages, setMessages] = useState([]);
const [input, setInput] = useState("");
const sendMessage = async () => {
const newMessages = [...messages, { role: "user", content: input }];
setMessages(newMessages);
setInput("");
const res = await fetch("/api/chat", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ messages: newMessages }),
});
const data = await res.json();
setMessages([...newMessages, { role: "assistant", content: data.content[0].text }]);
};
return (
<div>
{messages.map((m, i) => (
<p key={i}><strong>{m.role}:</strong> {m.content}</p>
))}
<input value={input} onChange={(e) => setInput(e.target.value)} />
<button onClick={sendMessage}>Send</button>
</div>
);
}
This is the whole pattern. Everything else — streaming, tool use, usage tracking — is a variation on this request/response loop.
Step 3: Stream responses into the UI
Waiting for a full response before showing anything feels slow, especially for longer answers. Streaming tokens as they arrive makes the app feel responsive even on bigger prompts. On the backend, forward the stream; on the frontend, read it incrementally.
async function sendMessageStreaming(messages, onToken) {
const res = await fetch("/api/chat/stream", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ messages }),
});
const reader = res.body.getReader();
const decoder = new TextDecoder();
while (true) {
const { done, value } = await reader.read();
if (done) break;
onToken(decoder.decode(value));
}
}
If your backend is a proxy you built yourself, you need to pipe Claude's SSE stream through your own route. If you're using SubToAPI, the streaming endpoint is already SSE-compatible, so your backend just forwards the raw stream from https://api.subtoapi.app/v1/messages with "stream": true — no transformation logic needed on your side. Details on the event format are in /docs/streaming.
Step 4: Add tool use if your app needs it
A lot of "Claude in a React app" integrations aren't pure chat — they're an assistant that can call functions in your product (search a database, create a record, fetch an order status). That's Claude's tool use feature: you describe available tools in the request, Claude returns a tool_use block instead of plain text, your backend executes the real function, and you send the result back as a tool_result message.
const response = await anthropic.messages.create({
model: "claude-sonnet-4-5",
max_tokens: 1024,
tools: [
{
name: "get_order_status",
description: "Look up an order by ID",
input_schema: {
type: "object",
properties: { order_id: { type: "string" } },
required: ["order_id"],
},
},
],
messages,
});
The React side doesn't need to know any of this happened — it just renders the final assistant message once your backend resolves the tool call loop. If you're routing through SubToAPI, tool definitions and tool_use responses work the same way through /v1/messages; see /docs/tools for the request shape.
Step 5: Keep key management out of the frontend permanently
Once the integration works, the easy mistake is loosening security later — someone adds a "quick test" that calls the API directly from a React effect with a hardcoded key "just for now." Set a rule early: no Claude credentials in any file that ships to the browser, full stop. Use environment variables on the server only, and if multiple developers or environments (staging, production, a client demo) need separate access, issue separate keys rather than sharing one.
This is the part SubToAPI is built for: instead of one shared secret, you get per-key scoping, usage metadata per request, and team seats so each developer or environment has its own sub_live_... key without touching your Anthropic console. Setup is a signup and an environment variable swap — see /docs/quickstart — and pricing starts at €9/month on the Solo plan, with Team and Scale plans for multiple keys per project; full details at /pricing.
questions
Do I need a backend to use Claude in a React app, or can I call the API from the browser? You need a backend or a hosted proxy. Claude's API key must never be exposed to the browser; a server-side route (or a service like SubToAPI) holds the key and forwards requests.
How do I show Claude's response as it's being generated instead of waiting for the full answer? Use streaming. Request "stream": true from Claude, forward the SSE stream through your backend route, and read it in React with response.body.getReader(), appending each chunk to state as it arrives.
Can I use Claude for actions inside my React app, not just chat? Yes, via tool use. Define tools with a JSON schema, let Claude return tool_use blocks when it wants to call one, execute the real function on your backend, and return the result as a tool_result message in the next request.