M·H·ABlog
AI·9 min

How to Integrate Claude AI with React: A Complete 2025 Guide

Step-by-step guide to adding Claude AI into a React application — from SDK setup to streaming responses and production error handling.

Muhammad Hamza Aftab
Muhammad Hamza Aftab
ReactAIClaudeTypeScript
Ad · 336×280 Rectangle

Building AI-powered features is no longer optional for competitive apps. Claude — Anthropic's frontier model — offers a best-in-class API that is safe, reliable, and genuinely useful for real product work. This guide walks you through a complete integration: from installing the SDK to streaming responses in a React UI.

Prerequisites

You need:

  • A React project (Next.js App Router or Vite, both work)
  • Node.js 18+ or Bun
  • An Anthropic API key from console.anthropic.com

Setting Up the Anthropic SDK

Install the official SDK:

bash
npm install @anthropic-ai/sdk
# or
bun add @anthropic-ai/sdk

Store your API key as an environment variable — never expose it client-side:

bash
# .env.local
ANTHROPIC_API_KEY=sk-ant-...

Creating a Server-Side API Route

Never call the Anthropic API directly from the browser. Create a server-side route instead. In Next.js App Router:

typescript
// app/api/chat/route.ts
import Anthropic from '@anthropic-ai/sdk';

const client = new Anthropic({
  apiKey: process.env.ANTHROPIC_API_KEY,
});

export async function POST(request: Request) {
  const { messages } = await request.json();

  const response = await client.messages.create({
    model: 'claude-sonnet-4-5',
    max_tokens: 1024,
    messages,
  });

  const text = response.content[0].type === 'text'
    ? response.content[0].text
    : '';

  return Response.json({ text });
}

Building the React Chat Component

Now wire up the frontend. Here's a minimal but complete chat component:

typescript
'use client';

import { useState } from 'react';

interface Message {
  role: 'user' | 'assistant';
  content: string;
}

export default function ChatComponent() {
  const [messages, setMessages] = useState<Message[]>([]);
  const [input, setInput] = useState('');
  const [loading, setLoading] = useState(false);

  async function sendMessage(e: React.FormEvent) {
    e.preventDefault();
    if (!input.trim() || loading) return;

    const userMsg: Message = { role: 'user', content: input };
    const newMessages = [...messages, userMsg];
    setMessages(newMessages);
    setInput('');
    setLoading(true);

    try {
      const res = await fetch('/api/chat', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ messages: newMessages }),
      });

      const { text } = await res.json();
      setMessages([...newMessages, { role: 'assistant', content: text }]);
    } catch (err) {
      console.error('Chat error:', err);
    } finally {
      setLoading(false);
    }
  }

  return (
    <div className="flex flex-col h-[600px] border rounded-xl overflow-hidden">
      {/* Message list */}
      <div className="flex-1 overflow-y-auto p-4 space-y-4">
        {messages.map((msg, i) => (
          <div key={i} className={msg.role === 'user' ? 'text-right' : 'text-left'}>
            <span className="inline-block px-4 py-2 rounded-lg max-w-[80%] text-sm">
              {msg.content}
            </span>
          </div>
        ))}
        {loading && <p className="text-sm text-muted">Claude is thinking...</p>}
      </div>

      {/* Input */}
      <form onSubmit={sendMessage} className="p-3 border-t flex gap-2">
        <input
          value={input}
          onChange={(e) => setInput(e.target.value)}
          placeholder="Ask Claude anything..."
          className="flex-1 px-3 py-2 rounded-lg border text-sm focus:outline-none"
        />
        <button type="submit" disabled={loading}>
          Send
        </button>
      </form>
    </div>
  );
}

Adding Streaming Responses

Waiting for the full response feels slow. Streaming tokens as they arrive makes the UI feel instant. Update the API route to stream:

typescript
// app/api/chat/stream/route.ts
import Anthropic from '@anthropic-ai/sdk';

const client = new Anthropic();

export async function POST(request: Request) {
  const { messages } = await request.json();

  const stream = await client.messages.stream({
    model: 'claude-sonnet-4-5',
    max_tokens: 1024,
    messages,
  });

  const encoder = new TextEncoder();

  const readable = new ReadableStream({
    async start(controller) {
      for await (const chunk of stream) {
        if (
          chunk.type === 'content_block_delta' &&
          chunk.delta.type === 'text_delta'
        ) {
          controller.enqueue(encoder.encode(chunk.delta.text));
        }
      }
      controller.close();
    },
  });

  return new Response(readable, {
    headers: { 'Content-Type': 'text/plain; charset=utf-8' },
  });
}

And update the React component to read the stream:

typescript
async function sendMessageStreaming(e: React.FormEvent) {
  e.preventDefault();
  if (!input.trim() || loading) return;

  const userMsg: Message = { role: 'user', content: input };
  const newMessages = [...messages, userMsg];
  setMessages([...newMessages, { role: 'assistant', content: '' }]);
  setInput('');
  setLoading(true);

  const res = await fetch('/api/chat/stream', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ messages: newMessages }),
  });

  const reader = res.body?.getReader();
  const decoder = new TextDecoder();

  if (!reader) return;

  let accumulated = '';
  while (true) {
    const { done, value } = await reader.read();
    if (done) break;
    accumulated += decoder.decode(value, { stream: true });
    setMessages((prev) => [
      ...prev.slice(0, -1),
      { role: 'assistant', content: accumulated },
    ]);
  }

  setLoading(false);
}

Error Handling and Rate Limits

Production apps need proper error handling. Anthropic's SDK throws typed errors:

typescript
import Anthropic from '@anthropic-ai/sdk';

try {
  const response = await client.messages.create({ ... });
} catch (error) {
  if (error instanceof Anthropic.APIError) {
    if (error.status === 429) {
      // Rate limited — implement exponential backoff
      console.error('Rate limited:', error.message);
    } else if (error.status === 401) {
      // Invalid API key
      console.error('Auth error:', error.message);
    } else {
      console.error('API error:', error.status, error.message);
    }
  }
}

What's Next

You now have a working Claude integration with streaming. From here:

  • Add a system prompt in the messages.create call to set Claude's persona
  • Implement conversation memory by persisting messages to a database
  • Add tool use to let Claude call your APIs
  • Set up usage tracking so you can monitor costs per user

Claude's API is powerful and the documentation at docs.anthropic.com is excellent — genuinely one of the better API docs in the industry.

Ad · 728×90 Leaderboard