This comprehensive guide walks you through integrating AI chatbots into your applications using three major providers: OpenAI, Anthropic Claude, and ElevenLabs. By the end, you will be able to build both text-based and voice-enabled conversational interfaces with Next.js.
Table of Contents
- Introduction
- Prerequisites
- OpenAI Integration
- Anthropic Claude Integration
- ElevenLabs Voice Integration
- Unified Chatbot with AI SDK
- Production Considerations
- Related Resources
Introduction
AI chatbots have become essential for modern applications, providing instant customer support, interactive documentation, and personalized user experiences. This guide covers three approaches to chatbot integration:
- OpenAI GPT - Industry-leading language models for text-based chat
- Anthropic Claude - Advanced reasoning and longer context windows
- ElevenLabs - Voice synthesis for audio-enabled chatbots
Each provider offers unique strengths, and you can combine them for a complete solution.
Prerequisites
Before starting, ensure you have:
- Node.js 18+ installed
- A Next.js 14+ project (App Router recommended)
- API keys from the providers you plan to use:
- OpenAI: platform.openai.com
- Anthropic: console.anthropic.com
- ElevenLabs: elevenlabs.io
Install the required dependencies:
# Core AI SDK (recommended for unified API)
npm install ai @ai-sdk/openai @ai-sdk/anthropic
# Direct provider SDKs (optional)
npm install openai @anthropic-ai/sdk elevenlabs
# ElevenLabs React components for voice
npm install @11labs/reactSet up your environment variables:
# .env.local
OPENAI_API_KEY=sk-...
ANTHROPIC_API_KEY=sk-ant-...
ELEVENLABS_API_KEY=...OpenAI Integration
OpenAI's GPT models are the most widely used for chatbot applications, offering excellent performance for general conversation, code assistance, and creative tasks.
Setting Up OpenAI
Create a basic OpenAI configuration:
// lib/openai.ts
import OpenAI from 'openai';
export const openai = new OpenAI({
apiKey: process.env.OPENAI_API_KEY,
});Building a Chat API Route
Create a Next.js API route that handles chat requests:
// app/api/chat/openai/route.ts
import { NextRequest, NextResponse } from 'next/server';
import OpenAI from 'openai';
const openai = new OpenAI({
apiKey: process.env.OPENAI_API_KEY,
});
export async function POST(req: NextRequest) {
try {
const { messages } = await req.json();
const completion = await openai.chat.completions.create({
model: 'gpt-4-turbo',
messages: [
{
role: 'system',
content: 'You are a helpful AI assistant. Be concise and friendly.',
},
...messages,
],
temperature: 0.7,
max_tokens: 1000,
});
const reply = completion.choices[0].message.content;
return NextResponse.json({
role: 'assistant',
content: reply
});
} catch (error: any) {
console.error('OpenAI API error:', error);
return NextResponse.json(
{ error: 'Failed to generate response' },
{ status: 500 }
);
}
}Streaming Responses
For a better user experience, implement streaming to show responses as they are generated:
// app/api/chat/openai/stream/route.ts
import { NextRequest } from 'next/server';
import OpenAI from 'openai';
const openai = new OpenAI({
apiKey: process.env.OPENAI_API_KEY,
});
export async function POST(req: NextRequest) {
const { messages } = await req.json();
const stream = await openai.chat.completions.create({
model: 'gpt-4-turbo',
messages: [
{
role: 'system',
content: 'You are a helpful AI assistant.',
},
...messages,
],
stream: true,
});
// Create a readable stream for the response
const encoder = new TextEncoder();
const readable = new ReadableStream({
async start(controller) {
for await (const chunk of stream) {
const content = chunk.choices[0]?.delta?.content || '';
if (content) {
controller.enqueue(encoder.encode(`data: ${JSON.stringify({ content })}\n\n`));
}
}
controller.enqueue(encoder.encode('data: [DONE]\n\n'));
controller.close();
},
});
return new Response(readable, {
headers: {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
'Connection': 'keep-alive',
},
});
}Anthropic Claude Integration
Anthropic's Claude models excel at nuanced reasoning, longer documents, and complex analysis. Claude offers a 200K token context window, making it ideal for document-heavy applications.
Setting Up Claude
// lib/anthropic.ts
import Anthropic from '@anthropic-ai/sdk';
export const anthropic = new Anthropic({
apiKey: process.env.ANTHROPIC_API_KEY,
});Claude API Route
// app/api/chat/claude/route.ts
import { NextRequest, NextResponse } from 'next/server';
import Anthropic from '@anthropic-ai/sdk';
const anthropic = new Anthropic({
apiKey: process.env.ANTHROPIC_API_KEY,
});
export async function POST(req: NextRequest) {
try {
const { messages } = await req.json();
// Convert messages to Claude format
const claudeMessages = messages.map((msg: { role: string; content: string }) => ({
role: msg.role === 'user' ? 'user' : 'assistant',
content: msg.content,
}));
const response = await anthropic.messages.create({
model: 'claude-3-5-sonnet-20241022',
max_tokens: 1024,
system: 'You are a helpful AI assistant. Provide thoughtful and detailed responses.',
messages: claudeMessages,
});
// Extract text from response
const textContent = response.content.find(block => block.type === 'text');
const reply = textContent?.text || '';
return NextResponse.json({
role: 'assistant',
content: reply,
});
} catch (error: any) {
console.error('Claude API error:', error);
return NextResponse.json(
{ error: 'Failed to generate response' },
{ status: 500 }
);
}
}Claude Streaming with Messages API
// app/api/chat/claude/stream/route.ts
import { NextRequest } from 'next/server';
import Anthropic from '@anthropic-ai/sdk';
const anthropic = new Anthropic({
apiKey: process.env.ANTHROPIC_API_KEY,
});
export async function POST(req: NextRequest) {
const { messages } = await req.json();
const claudeMessages = messages.map((msg: { role: string; content: string }) => ({
role: msg.role === 'user' ? 'user' : 'assistant',
content: msg.content,
}));
const stream = await anthropic.messages.stream({
model: 'claude-3-5-sonnet-20241022',
max_tokens: 1024,
system: 'You are a helpful AI assistant.',
messages: claudeMessages,
});
const encoder = new TextEncoder();
const readable = new ReadableStream({
async start(controller) {
for await (const event of stream) {
if (event.type === 'content_block_delta' && event.delta.type === 'text_delta') {
const content = event.delta.text;
controller.enqueue(encoder.encode(`data: ${JSON.stringify({ content })}\n\n`));
}
}
controller.enqueue(encoder.encode('data: [DONE]\n\n'));
controller.close();
},
});
return new Response(readable, {
headers: {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
'Connection': 'keep-alive',
},
});
}ElevenLabs Voice Integration
ElevenLabs provides industry-leading text-to-speech and conversational AI capabilities, enabling voice-enabled chatbots with natural-sounding speech.
Text-to-Speech Setup
Convert chatbot responses to audio:
// app/api/speak/route.ts
import { NextRequest, NextResponse } from 'next/server';
export async function POST(req: NextRequest) {
try {
const { text, voiceId = 'EXAVITQu4vr4xnSDxMaL' } = await req.json();
const response = await fetch(
`https://api.elevenlabs.io/v1/text-to-speech/${voiceId}`,
{
method: 'POST',
headers: {
'xi-api-key': process.env.ELEVENLABS_API_KEY!,
'Content-Type': 'application/json',
},
body: JSON.stringify({
text,
model_id: 'eleven_monolingual_v1',
voice_settings: {
stability: 0.5,
similarity_boost: 0.75,
},
}),
}
);
if (!response.ok) {
throw new Error(`ElevenLabs API error: ${response.statusText}`);
}
const audioBuffer = await response.arrayBuffer();
return new NextResponse(audioBuffer, {
headers: {
'Content-Type': 'audio/mpeg',
},
});
} catch (error: any) {
console.error('Text-to-speech error:', error);
return NextResponse.json(
{ error: 'Failed to generate audio' },
{ status: 500 }
);
}
}Voice-Enabled Chatbot
Combine text chat with voice output:
// components/VoiceChatbot.tsx
'use client';
import { useState, useRef } from 'react';
interface Message {
role: 'user' | 'assistant';
content: string;
}
export function VoiceChatbot() {
const [messages, setMessages] = useState<Message[]>([]);
const [input, setInput] = useState('');
const [isLoading, setIsLoading] = useState(false);
const audioRef = useRef<HTMLAudioElement>(null);
const sendMessage = async () => {
if (!input.trim()) return;
const userMessage: Message = { role: 'user', content: input };
setMessages((prev) => [...prev, userMessage]);
setInput('');
setIsLoading(true);
try {
// Get text response from AI
const chatResponse = await fetch('/api/chat/openai', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
messages: [...messages, userMessage],
}),
});
const { content } = await chatResponse.json();
const assistantMessage: Message = { role: 'assistant', content };
setMessages((prev) => [...prev, assistantMessage]);
// Convert response to speech
const audioResponse = await fetch('/api/speak', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ text: content }),
});
if (audioResponse.ok) {
const audioBlob = await audioResponse.blob();
const audioUrl = URL.createObjectURL(audioBlob);
if (audioRef.current) {
audioRef.current.src = audioUrl;
audioRef.current.play();
}
}
} catch (error) {
console.error('Chat error:', error);
} finally {
setIsLoading(false);
}
};
return (
<div className="flex flex-col h-[600px] max-w-2xl mx-auto border rounded-lg">
<div className="flex-1 overflow-y-auto p-4 space-y-4">
{messages.map((msg, idx) => (
<div
key={idx}
className={`p-3 rounded-lg ${
msg.role === 'user'
? 'bg-blue-100 ml-auto max-w-[80%]'
: 'bg-gray-100 mr-auto max-w-[80%]'
}`}
>
{msg.content}
</div>
))}
{isLoading && (
<div className="bg-gray-100 p-3 rounded-lg mr-auto">
Thinking...
</div>
)}
</div>
<div className="p-4 border-t flex gap-2">
<input
type="text"
value={input}
onChange={(e) => setInput(e.target.value)}
onKeyPress={(e) => e.key === 'Enter' && sendMessage()}
placeholder="Type your message..."
className="flex-1 p-2 border rounded"
/>
<button
onClick={sendMessage}
disabled={isLoading}
className="px-4 py-2 bg-blue-500 text-white rounded disabled:bg-gray-300"
>
Send
</button>
</div>
<audio ref={audioRef} className="hidden" />
</div>
);
}Real-Time Conversations
For truly interactive voice conversations, use the ElevenLabs Conversational AI SDK:
// components/RealtimeVoiceChat.tsx
'use client';
import { useConversation } from '@11labs/react';
import { useCallback, useState } from 'react';
export function RealtimeVoiceChat() {
const [transcript, setTranscript] = useState<string[]>([]);
const conversation = useConversation({
onConnect: () => console.log('Connected to ElevenLabs'),
onDisconnect: () => console.log('Disconnected'),
onMessage: (message) => {
setTranscript((prev) => [...prev, `AI: ${message.message}`]);
},
onError: (error) => console.error('Conversation error:', error),
});
const startConversation = useCallback(async () => {
try {
// Request microphone access
await navigator.mediaDevices.getUserMedia({ audio: true });
// Start the conversation session
await conversation.startSession({
agentId: process.env.NEXT_PUBLIC_ELEVENLABS_AGENT_ID!,
});
} catch (error) {
console.error('Failed to start conversation:', error);
}
}, [conversation]);
const stopConversation = useCallback(async () => {
await conversation.endSession();
}, [conversation]);
return (
<div className="flex flex-col items-center gap-6 p-6">
<h2 className="text-2xl font-bold">Real-Time Voice Chat</h2>
<div className="flex gap-4">
<button
onClick={startConversation}
disabled={conversation.status === 'connected'}
className="px-6 py-3 bg-green-500 text-white rounded-lg disabled:bg-gray-300 transition-colors"
>
Start Conversation
</button>
<button
onClick={stopConversation}
disabled={conversation.status !== 'connected'}
className="px-6 py-3 bg-red-500 text-white rounded-lg disabled:bg-gray-300 transition-colors"
>
End Conversation
</button>
</div>
<div className="text-center">
<p className="text-lg">
Status: <span className="font-semibold">{conversation.status}</span>
</p>
<p className="text-sm text-gray-600">
{conversation.isSpeaking ? 'AI is speaking...' : 'Listening...'}
</p>
</div>
<div className="w-full max-w-md h-64 overflow-y-auto border rounded p-4">
{transcript.map((line, idx) => (
<p key={idx} className="mb-2">{line}</p>
))}
</div>
</div>
);
}Unified Chatbot with AI SDK
The Vercel AI SDK provides a unified API for multiple providers, making it easy to switch between models or combine them:
// app/api/chat/route.ts
import { streamText } from 'ai';
import { openai } from '@ai-sdk/openai';
import { anthropic } from '@ai-sdk/anthropic';
export async function POST(req: Request) {
const { messages, provider = 'openai' } = await req.json();
// Select model based on provider
const model = provider === 'anthropic'
? anthropic('claude-3-5-sonnet-20241022')
: openai('gpt-4-turbo');
const result = await streamText({
model,
system: 'You are a helpful AI assistant.',
messages,
});
return result.toAIStreamResponse();
}// app/chat/page.tsx
'use client';
import { useChat } from 'ai/react';
import { useState } from 'react';
export default function ChatPage() {
const [provider, setProvider] = useState<'openai' | 'anthropic'>('openai');
const { messages, input, handleInputChange, handleSubmit, isLoading } = useChat({
body: { provider },
});
return (
<div className="max-w-2xl mx-auto p-4">
<div className="mb-4 flex gap-2">
<button
onClick={() => setProvider('openai')}
className={`px-4 py-2 rounded ${
provider === 'openai' ? 'bg-blue-500 text-white' : 'bg-gray-200'
}`}
>
OpenAI
</button>
<button
onClick={() => setProvider('anthropic')}
className={`px-4 py-2 rounded ${
provider === 'anthropic' ? 'bg-purple-500 text-white' : 'bg-gray-200'
}`}
>
Claude
</button>
</div>
<div className="h-[500px] overflow-y-auto border rounded p-4 mb-4">
{messages.map((m) => (
<div
key={m.id}
className={`mb-4 p-3 rounded ${
m.role === 'user' ? 'bg-blue-100 ml-8' : 'bg-gray-100 mr-8'
}`}
>
<strong>{m.role === 'user' ? 'You' : 'AI'}:</strong> {m.content}
</div>
))}
</div>
<form onSubmit={handleSubmit} className="flex gap-2">
<input
value={input}
onChange={handleInputChange}
placeholder="Type your message..."
className="flex-1 p-2 border rounded"
disabled={isLoading}
/>
<button
type="submit"
disabled={isLoading}
className="px-4 py-2 bg-blue-500 text-white rounded disabled:bg-gray-300"
>
{isLoading ? 'Sending...' : 'Send'}
</button>
</form>
</div>
);
}Production Considerations
When deploying AI chatbots to production, consider these important factors:
Rate Limiting
Protect your API routes from abuse:
// middleware.ts (example with rate limiting)
import { Ratelimit } from '@upstash/ratelimit';
import { Redis } from '@upstash/redis';
const ratelimit = new Ratelimit({
redis: Redis.fromEnv(),
limiter: Ratelimit.slidingWindow(10, '1 m'), // 10 requests per minute
});
export async function middleware(req: Request) {
const ip = req.headers.get('x-forwarded-for') ?? '127.0.0.1';
const { success } = await ratelimit.limit(ip);
if (!success) {
return new Response('Too many requests', { status: 429 });
}
}Error Handling Best Practices
// lib/ai-error-handler.ts
export function handleAIError(error: any): Response {
// Rate limit errors
if (error.status === 429) {
return new Response(
JSON.stringify({ error: 'Rate limit exceeded. Please try again later.' }),
{ status: 429 }
);
}
// Token limit errors
if (error.message?.includes('context_length')) {
return new Response(
JSON.stringify({ error: 'Message too long. Please shorten your input.' }),
{ status: 400 }
);
}
// Generic error
return new Response(
JSON.stringify({ error: 'An error occurred. Please try again.' }),
{ status: 500 }
);
}Cost Optimization Tips
- Use appropriate models - GPT-3.5-turbo is cheaper than GPT-4 for simple tasks
- Implement caching - Cache common responses to reduce API calls
- Set max_tokens - Limit response length to control costs
- Use streaming - Better UX without additional cost
- Monitor usage - Set up alerts for unexpected spikes
Related Resources
AI SDK Tutorials
For more in-depth tutorials on AI SDKs and frameworks:
- AI SDK Tutorial Hub - Complete guide to AI SDKs organized by difficulty
- Building Conversational AI with Next.js - Voice-enabled chatbots with ElevenLabs
- Fine-tuning GPT with Vercel AI SDK - Custom model training
AI Content Hub
Explore our comprehensive AI and automation resources:
- AI and Automation Solutions Hub - Central hub for all AI content
- AI and Automation in Web Development - How AI transforms development
- Claude AI Excellence Guide - Deep dive into Anthropic Claude
Protocol Resources
- Understanding MCP and A2A - AI context protocols
- Introduction to MCP - Getting started with Model Context Protocol
Conclusion
You now have the knowledge to integrate AI chatbots using OpenAI, Anthropic Claude, and ElevenLabs. Start with a simple text-based implementation and gradually add features like streaming, voice synthesis, and real-time conversations.
The Vercel AI SDK simplifies multi-provider integration, making it easy to experiment with different models and switch providers based on your needs.
Reference: This guide covers integration patterns used in production applications. For official documentation, visit OpenAI, Anthropic, and ElevenLabs.