Why Combine ElevenLabs with Next.js?
If you’ve ever wanted to add natural‑sounding voice to a web app—whether it’s for accessibility, dynamic podcasts, or a chatbot—ElevenLabs makes it surprisingly easy. Their text‑to‑speech (TTS) engine delivers lifelike speech and even supports voice cloning, so you can give your product a unique vocal personality.
Next.js is the go‑to React framework for server‑side rendering, API routes, and edge‑ready deployments. Pairing it with ElevenLabs lets you keep the heavy lifting (API calls, auth) on the server while the client streams the generated audio instantly. In this guide we’ll walk through a complete, production‑ready integration from scratch.
Quick note: Sign up for ElevenLabs using this affiliate link to get free credits and support the article: https://try.elevenlabs.io/kr07zfuqn1bp
Prerequisites
| What you need | Why it matters |
|---|---|
| Node.js ≥ 18 | Modern fetch API and ESM support |
| Next.js 13+ (app router) | Simplifies server components and streaming |
| ElevenLabs API key | Authenticates your requests |
| Basic React knowledge | To build the UI component |
If you don’t have a Next.js project yet, spin one up in a minute:
npx create-next-app@latest elevenlabs-demo
cd elevenlabs-demo
npm run dev
1. Grab Your ElevenLabs API Key
- Visit the ElevenLabs dashboard (via the affiliate link above).
- Create a new project or use the default one.
- Copy the API Key—you’ll need it for server‑side calls.
Store the key safely in a .env.local file:
ELEVENLABS_API_KEY=your-secret-key-here
Never commit this file; Next.js automatically excludes
.env.localfrom version control.
2. Build a Server‑Side API Route
Next.js API routes run on the server, making them perfect for keeping your API key hidden. Create a new file at app/api/tts/route.js (or pages/api/tts.js if you’re on the pages router).
// app/api/tts/route.js
import { NextResponse } from 'next/server';
export async function POST(request) {
const { text, voice_id = '21m00Tcm4TlvDq8ikWAM' } = await request.json();
// Forward the request to ElevenLabs
const elevenResponse = await fetch(
`https://api.elevenlabs.io/v1/text-to-speech/${voice_id}`,
{
method: 'POST',
headers: {
'xi-api-key': process.env.ELEVENLABS_API_KEY,
'Content-Type': 'application/json',
Accept: 'audio/mpeg',
},
body: JSON.stringify({
text,
voice_settings: {
stability: 0.75,
similarity_boost: 0.85,
},
}),
}
);
if (!elevenResponse.ok) {
const err = await elevenResponse.text();
return NextResponse.json({ error: err }, { status: 500 });
}
// Stream the audio straight back to the client
const audioStream = elevenResponse.body;
return new NextResponse(audioStream, {
headers: {
'Content-Type': 'audio/mpeg',
},
});
}
What’s happening?
-
POST body:
{ text, voice_id }– the text you want spoken and optionally a different voice. -
Fetch: Calls ElevenLabs’
/text-to-speech/{voice_id}endpoint. The request returns an MP3 stream. - Response: We pipe that MP3 directly back to the browser, avoiding temporary files.
3. Front‑End Component: Talk to the API
Now let’s create a React component that lets users type a sentence and hear it instantly.
// components/VoiceSynthesizer.tsx
'use client';
import { useState, useRef } from 'react';
export default function VoiceSynthesizer() {
const [text, setText] = useState('');
const [loading, setLoading] = useState(false);
const audioRef = useRef<HTMLAudioElement>(null);
const handleSpeak = async () => {
if (!text.trim()) return;
setLoading(true);
const res = await fetch('/api/tts', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ text }),
});
if (!res.ok) {
const err = await res.text();
alert('Error: ' + err);
setLoading(false);
return;
}
// Create a blob URL for the streamed audio
const blob = await res.blob();
const url = URL.createObjectURL(blob);
audioRef.current!.src = url;
audioRef.current!.play();
setLoading(false);
};
return (
<div style={{ maxWidth: '600px', margin: 'auto' }}>
<h2>🗣️ Text‑to‑Speech Demo</h2>
<textarea
rows={4}
placeholder="Enter text to speak..."
value={text}
onChange={(e) => setText(e.target.value)}
style={{ width: '100%', padding: '0.5rem' }}
/>
<button
onClick={handleSpeak}
disabled={loading}
style={{
marginTop: '0.5rem',
padding: '0.5rem 1rem',
cursor: loading ? 'wait' : 'pointer',
}}
>
{loading ? 'Synthesizing…' : 'Speak'}
</button>
<audio ref={audioRef} controls style={{ marginTop: '1rem', width: '100%' }} />
</div>
);
}
Add the component to any page, for example app/page.tsx:
import VoiceSynthesizer from '@/components/VoiceSynthesizer';
export default function Home() {
return (
<main>
<VoiceSynthesizer />
</main>
);
}
Why use a blob URL?
ElevenLabs streams MP3 data; converting it to a Blob lets the <audio> element handle playback without extra libraries. The approach works both locally and on Vercel edge functions.
4. Testing with curl
Before wiring the UI, you can verify the API route with a simple curl command:
curl -X POST http://localhost:3000/api/tts \
-H "Content-Type: application/json" \
-d '{"text":"Hello from ElevenLabs!"}' \
--output speech.mp3
If speech.mp3 plays correctly, your server route is good to go.
5. Deploying to Vercel (or any Node host)
When you push to GitHub and link the repo to Vercel:
-
Add the environment variable
ELEVENLABS_API_KEYin the Vercel dashboard. - Vercel will automatically build the Next.js app; the API route remains server‑only, keeping the key safe.
Tip: For lower latency, switch the API route to an edge function (export const runtime = 'edge';) and use the fetch API with keepalive: true. ElevenLabs’ endpoints are already globally distributed, so you’ll see sub‑second response times for short sentences.
6. Going Further: Voice Cloning
ElevenLabs also supports custom voice cloning. After uploading a few minutes of reference audio, you receive a voice_id. Plug that ID into the request payload:
{
"text": "Your custom voice speaks!",
"voice_id": "your-cloned-voice-id"
}
You can expose a dropdown of available voices in the UI, letting users pick a brand‑specific tone. The same API route works—just pass the ID from the client.
7. Common Pitfalls & Debugging
| Symptom | Likely Cause | Fix |
|---|---|---|
401 Unauthorized from ElevenLabs |
Wrong or missing API key | Ensure ELEVENLABS_API_KEY is set in .env.local and Vercel env. |
| No audio, just silence |
Accept: audio/mpeg header missing |
Verify the header is present in the fetch call. |
| “Failed to fetch” in the browser | CORS or network error | The API route runs on the same origin, so CORS isn’t an issue. Check that the server is running and the route path matches. |
| Large latency (>5 s) | Long text or heavy server load | Break long passages into chunks and stream them sequentially. |
8. Wrapping Up
You now have a full‑stack voice AI feature powered by ElevenLabs and Next.js:
- Secure server‑side API route that streams MP3 directly to the client.
- React component that handles user input, fetches audio, and plays it instantly.
- Ready‑to‑deploy setup that works on Vercel, Netlify, or any Node host.
The combination is powerful for:
- Audio blogs – generate spoken versions of articles on the fly.
- Interactive assistants – give chatbots a consistent, human‑like voice.
- Accessibility tools – read UI content for screen‑reader users.
Ready to give your app a voice? Sign up for ElevenLabs via this link and start experimenting: https://try.elevenlabs.io/kr07zfuqn1bp
Happy coding, and may your apps speak as clearly as your code!
Top comments (0)