Table of Contents
Integrating Claude API with Next.js: A Complete Guide
Learn how to seamlessly integrate Anthropic's Claude AI capabilities into your Next.js applications for enhanced user experiences and AI-powered features.
🤖 Introduction to Claude and Next.js
In today's rapidly evolving tech landscape, AI has become an indispensable tool for developers looking to create more intelligent, responsive applications. Anthropic's Claude, a powerful AI assistant, offers sophisticated natural language processing capabilities that can significantly enhance web applications. When combined with Next.js—React's popular framework for building modern web applications—developers can create cutting-edge AI-powered experiences with relative ease.
This guide will walk you through the process of integrating the Claude API with your Next.js application, from setting up the necessary environment to implementing practical features. Whether you're looking to build a chatbot, analyze documents, or generate content, this integration will provide the foundation you need.
Image suggestion: A visual diagram showing Claude API connecting to a Next.js application, with data flowing between them.
🛠 Setting Up Your Development Environment
Before diving into code, let's ensure you have everything you need to get started with Claude API integration in your Next.js project.
Prerequisites
- Node.js (version 14.x or later)
- npm or yarn package manager
- A Next.js project (existing or new)
- An Anthropic API key (sign up at Anthropic's developer portal)
Creating a New Next.js Project
If you don't already have a Next.js project, you can create one using the following command:
npx create-next-app@latest claude-integration
cd claude-integration
Installing Required Dependencies
To work with the Claude API, you'll need to install the Anthropic JavaScript SDK:
npm install @anthropic-ai/sdk
# or
yarn add @anthropic-ai/sdk
For better development experience, also consider adding these helpful packages:
npm install dotenv
# or
yarn add dotenv
Setting Up Environment Variables
Create a .env.local
file in your project root to securely store your API key:
ANTHROPIC_API_KEY=your_api_key_here
To ensure Next.js can access these environment variables, create or update next.config.js
:
/** @type {import('next').NextConfig} */
const nextConfig = {
reactStrictMode: true,
env: {
ANTHROPIC_API_KEY: process.env.ANTHROPIC_API_KEY,
},
}
module.exports = nextConfig
Important note: Never commit your API keys to version control. Make sure .env.local
is included in your .gitignore
file.
🔌 Basic Claude API Integration
Let's start with a basic integration to understand how Claude API works with Next.js.
Creating an API Route
First, create an API route in your Next.js application to handle Claude API requests. Create a file at pages/api/claude.js
:
import Anthropic from '@anthropic-ai/sdk';
export default async function handler(req, res) {
if (req.method !== 'POST') {
return res.status(405).json({ message: 'Method not allowed' });
}
try {
const { prompt } = req.body;
if (!prompt) {
return res.status(400).json({ message: 'Prompt is required' });
}
const anthropic = new Anthropic({
apiKey: process.env.ANTHROPIC_API_KEY,
});
const completion = await anthropic.completions.create({
model: 'claude-3-sonnet-20240229',
max_tokens_to_sample: 1000,
prompt: `\n\nHuman: ${prompt}\n\nAssistant:`,
});
return res.status(200).json({ response: completion.completion });
} catch (error) {
console.error('Error calling Claude API:', error);
return res.status(500).json({ message: 'Error processing your request', error: error.message });
}
}
Creating a Simple Frontend Interface
Now, let's create a simple interface to interact with our Claude API endpoint. Create or modify pages/index.js
:
import { useState } from 'react';
import Head from 'next/head';
import styles from '../styles/Home.module.css';
export default function Home() {
const [prompt, setPrompt] = useState('');
const [response, setResponse] = useState('');
const [isLoading, setIsLoading] = useState(false);
const handleSubmit = async (e) => {
e.preventDefault();
setIsLoading(true);
try {
const res = await fetch('/api/claude', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ prompt }),
});
const data = await res.json();
if (res.ok) {
setResponse(data.response);
} else {
setResponse(`Error: ${data.message}`);
}
} catch (error) {
setResponse(`Error: ${error.message}`);
} finally {
setIsLoading(false);
}
};
return (
<div className={styles.container}>
<Head>
<title>Claude AI Integration</title>
<meta name="description" content="Next.js application with Claude AI integration" />
<link rel="icon" href="/favicon.ico" />
</Head>
<main className={styles.main}>
<h1 className={styles.title}>Claude AI Integration</h1>
<form onSubmit={handleSubmit} className={styles.form}>
<textarea
value={prompt}
onChange={(e) => setPrompt(e.target.value)}
placeholder="Enter your prompt here..."
rows={5}
className={styles.textarea}
required
/>
<button
type="submit"
className={styles.button}
disabled={isLoading}
>
{isLoading ? 'Processing...' : 'Submit'}
</button>
</form>
{response && (
<div className={styles.response}>
<h2>Claude's Response:</h2>
<p>{response}</p>
</div>
)}
</main>
</div>
);
}
Image suggestion: Screenshot of the simple UI showing a prompt input area and response display.
🔄 Using Claude's Streaming API for Real-time Responses
One of the most engaging ways to interact with AI is through streaming responses, where text appears incrementally as it's generated. Let's implement streaming with Claude API in Next.js.
Creating a Streaming API Route
Create a new file at pages/api/claude-stream.js
:
import Anthropic from '@anthropic-ai/sdk';
export default async function handler(req, res) {
if (req.method !== 'POST') {
return res.status(405).json({ message: 'Method not allowed' });
}
const { prompt } = req.body;
if (!prompt) {
return res.status(400).json({ message: 'Prompt is required' });
}
// Set up streaming headers
res.writeHead(200, {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache, no-transform',
'Connection': 'keep-alive',
});
try {
const anthropic = new Anthropic({
apiKey: process.env.ANTHROPIC_API_KEY,
});
const stream = await anthropic.completions.create({
model: 'claude-3-sonnet-20240229',
max_tokens_to_sample: 1000,
prompt: `\n\nHuman: ${prompt}\n\nAssistant:`,
stream: true,
});
for await (const completion of stream) {
// Send each chunk as it arrives
res.write(`data: ${JSON.stringify({ text: completion.completion })}\n\n`);
}
// End the stream
res.write(`data: ${JSON.stringify({ done: true })}\n\n`);
res.end();
} catch (error) {
console.error('Error calling Claude API:', error);
res.write(`data: ${JSON.stringify({ error: error.message })}\n\n`);
res.end();
}
}
Implementing a Streaming Frontend Component
Now, let's create a component that consumes our streaming API. Create a file at components/ClaudeStream.js
:
import { useState, useEffect, useRef } from 'react';
import styles from '../styles/ClaudeStream.module.css';
export default function ClaudeStream() {
const [prompt, setPrompt] = useState('');
const [response, setResponse] = useState('');
const [isStreaming, setIsStreaming] = useState(false);
const eventSourceRef = useRef(null);
const handleSubmit = async (e) => {
e.preventDefault();
// Reset previous response
setResponse('');
setIsStreaming(true);
// Close any existing connection
if (eventSourceRef.current) {
eventSourceRef.current.close();
}
try {
// First, send the prompt to the server
const res = await fetch('/api/claude-stream', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ prompt }),
});
if (!res.ok) {
throw new Error(`Server responded with ${res.status}`);
}
// Set up event source to receive streaming response
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\n');
for (const line of lines) {
if (line.startsWith('data: ')) {
const data = JSON.parse(line.substring(6));
if (data.done) {
setIsStreaming(false);
break;
}
if (data.error) {
setResponse(prev => prev + `\nError: ${data.error}`);
setIsStreaming(false);
break;
}
if (data.text) {
setResponse(prev => prev + data.text);
}
}
}
}
} catch (error) {
console.error('Error:', error);
setResponse(`Error: ${error.message}`);
setIsStreaming(false);
}
};
// Clean up on unmount
useEffect(() => {
return () => {
if (eventSourceRef.current) {
eventSourceRef.current.close();
}
};
}, []);
return (
<div className={styles.container}>
<h2 className={styles.title}>Chat with Claude (Streaming)</h2>
<form onSubmit={handleSubmit} className={styles.form}>
<textarea
value={prompt}
onChange={(e) => setPrompt(e.target.value)}
placeholder="Ask Claude something..."
rows={4}
className={styles.textarea}
disabled={isStreaming}
required
/>
<button
type="submit"
className={styles.button}
disabled={isStreaming || !prompt.trim()}
>
{isStreaming ? 'Streaming...' : 'Send'}
</button>
</form>
{(response || isStreaming) && (
<div className={styles.responseContainer}>
<h3>Claude's Response:</h3>
<div className={styles.response}>
{response}
{isStreaming && <span className={styles.cursor}>|</span>}
</div>
</div>
)}
</div>
);
}
Update your pages/index.js
to include this new component:
import ClaudeStream from '../components/ClaudeStream';
// ... other imports
export default function Home() {
// ... existing code
return (
<div className={styles.container}>
<Head>
<title>Claude AI Integration</title>
<meta name="description" content="Next.js application with Claude AI integration" />
<link rel="icon" href="/favicon.ico" />
</Head>
<main className={styles.main}>
<h1 className={styles.title}>Claude AI Integration</h1>
<ClaudeStream />
{/* You can keep your original non-streaming implementation as well */}
</main>
</div>
);
}
Image suggestion: Animated GIF showing the streaming response in action, with text appearing gradually.
📄 Building a Document Analysis Feature with Claude
One of Claude's powerful capabilities is document analysis. Let's create a feature that allows users to upload documents and get insights from Claude.
Setting Up File Upload
First, install necessary packages for file handling:
npm install formidable
# or
yarn add formidable
Create an API route for document analysis at pages/api/analyze-document.js
:
import formidable from 'formidable';
import fs from 'fs';
import Anthropic from '@anthropic-ai/sdk';
// Disable body parsing, we'll handle it with formidable
export const config = {
api: {
bodyParser: false,
},
};
export default async function handler(req, res) {
if (req.method !== 'POST') {
return res.status(405).json({ message: 'Method not allowed' });
}
try {
// Parse the form data
const form = new formidable.IncomingForm();
// Get the file and fields from the form
const [fields, files] = await new Promise((resolve, reject) => {
form.parse(req, (err, fields, files) => {
if (err) reject(err);
resolve([fields, files]);
});
});
// Get the file
const file = files.document;
if (!file) {
return res.status(400).json({ message: 'No document uploaded' });
}
// Read the file content
const fileContent = fs.readFileSync(file.filepath, 'utf8');
// Get the analysis question
const { question } = fields;
if (!question) {
return res.status(400).json({ message: 'Analysis question is required' });
}
// Initialize Anthropic client
const anthropic = new Anthropic({
apiKey: process.env.ANTHROPIC_API_KEY,
});
// Send the document and question to Claude
const completion = await anthropic.completions.create({
model: 'claude-3-sonnet-20240229',
max_tokens_to_sample: 2000,
prompt: `\n\nHuman: I'm going to share a document with you, and I'd like you to analyze it based on this question: ${question}\n\nHere's the document:\n\n${fileContent}\n\nAssistant:`,
});
return res.status(200).json({ analysis: completion.completion });
} catch (error) {
console.error('Error analyzing document:', error);
return res.status(500).json({ message: 'Error analyzing document', error: error.message });
}
}
Written by
Marcus Ruud
At
Mon Nov 13 2023