Why Voice AI Matters for Modern Web Apps
Voice has become a core interaction channel—think voice assistants, accessibility features, or just a hands‑free way to read content. If you’re building a Next.js project that needs natural‑sounding speech, you’ll want a service that’s fast, flexible, and easy to integrate. That’s where ElevenLabs shines. Their API delivers high‑quality, customizable text‑to‑speech (TTS) and voice‑cloning capabilities that fit right into a server‑rendered or static‑site‑generated Next.js app.
Below is a step‑by‑step guide to hooking up ElevenLabs into a Next.js project. We’ll cover:
- Getting your API key
- Setting up a secure API route in Next.js
- Streaming audio back to the client
- Optional: Cloning a custom voice
- Deploying to Vercel
1. Grab Your ElevenLabs API Key
Sign up at ElevenLabs. Once you’re in, navigate to the API section and copy the key. Store it in a .env.local file so it never ends up in version control:
# .env.local
ELEVENLABS_API_KEY=sk_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
Tip: Use the same link each time—
https://try.elevenlabs.io/kr07zfuqn1bp—so you can share it with teammates or refer back in documentation.
2. Create a Secure API Route
Next.js API routes are great for keeping your secrets on the server side. Create pages/api/speech.js:
// pages/api/speech.js
export default async function handler(req, res) {
if (req.method !== 'POST') {
return res.status(405).json({ error: 'Method not allowed' });
}
const { text, voiceId } = req.body;
if (!text) {
return res.status(400).json({ error: 'Missing text' });
}
try {
const response = await fetch('https://api.elevenlabs.io/v1/text-to-speech/' + voiceId, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'xi-api-key': process.env.ELEVENLABS_API_KEY,
},
body: JSON.stringify({
text,
voice_settings: {
stability: 0.5,
similarity_boost: 0.75,
},
}),
});
// ElevenLabs streams audio in raw PCM format
const audioBuffer = await response.arrayBuffer();
const audioBlob = new Blob([audioBuffer], { type: 'audio/mpeg' });
const base64Audio = Buffer.from(audioBuffer).toString('base64');
res.status(200).json({ audioBase64: base64Audio });
} catch (err) {
console.error(err);
res.status(500).json({ error: 'Internal server error' });
}
}
What’s Happening?
- Secure: The API key stays on the server.
- Streaming: We fetch the raw audio from ElevenLabs and return it as a base‑64 string.
-
Customizable:
voice_settingslet you tweak stability and similarity boost to fine‑tune the output.
3. Hook It Up on the Frontend
Now that the API route is ready, you can call it from any component. Here’s a simple form that turns text into speech:
// components/TextToSpeech.jsx
import { useState } from 'react';
export default function TextToSpeech() {
const [text, setText] = useState('');
const [audioSrc, setAudioSrc] = useState(null);
const [loading, setLoading] = useState(false);
const handleSpeak = async () => {
setLoading(true);
const res = await fetch('/api/speech', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ text, voiceId: 'EXAVITQu4vr4xnSDxMaL' }), // replace with your voice ID
});
const data = await res.json();
setAudioSrc(`data:audio/mpeg;base64,${data.audioBase64}`);
setLoading(false);
};
return (
<div>
<textarea
rows={4}
value={text}
onChange={(e) => setText(e.target.value)}
placeholder="Type something to hear it"
style={{ width: '100%' }}
/>
<button onClick={handleSpeak} disabled={loading || !text}>
{loading ? 'Speaking…' : 'Speak'}
</button>
{audioSrc && <audio controls src={audioSrc} />}
</div>
);
}
Pro Tip: If you’re building a large‑scale app, consider caching audio blobs in a CDN or using
Cache-Controlheaders to avoid repeated calls for the same text.
4. Going Beyond: Voice Cloning
ElevenLabs offers voice cloning, letting you create a synthetic voice that sounds like a real person. The process is similar to the basic TTS call, but you’ll use the voice‑creation endpoint.
curl -X POST https://api.elevenlabs.io/v1/voices \
-H "xi-api-key: $ELEVENLABS_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "My Clone",
"sample_text": "Hello, this is a test of the voice cloning service."
}'
The response will give you a voice_id. Store that ID and use it in your pages/api/speech.js call instead of the default voice. You can also upload a few minutes of audio to improve fidelity.
Note: Voice cloning requires a higher tier plan, so check the pricing on the ElevenLabs site.
5. Deploying to Vercel
Next.js is built for Vercel, so deployment is a breeze:
- Push your repo to GitHub.
- In the Vercel dashboard, import the repository.
- Add the environment variable
ELEVENLABS_API_KEYunder Settings → Environment Variables. - Deploy!
Once live, your users will experience real‑time TTS without exposing your API key.
Recap & Quick Checklist
- [ ] Sign up at ElevenLabs using the affiliate link
- [ ] Store your API key in
.env.local - [ ] Build a secure
/api/speechroute - [ ] Consume the route from a React component
- Clone a custom voice and replace the default ID
- [ ] Deploy to Vercel with env vars set
Next Steps
- Experiment with different
voice_settingsto find the sweet spot for your app. - Add a dropdown to let users pick from multiple voices.
- Cache generated audio on the client or a CDN for repeated phrases.
- If you’re building a chatbot, feed the TTS output directly into the chat UI.
Try ElevenLabs Today
If you’re ready to bring natural‑sounding speech to your Next.js projects, head over to the ElevenLabs portal with this link: https://try.elevenlabs.io/kr07zfuqn1bp. Sign up, grab your API key, and start building voice experiences that feel human. Happy coding!
Top comments (1)
Keeping the key behind the /api/speech route is a good call for a tutorial like this.
One small thing worth checking: the notes describe it as streaming, but returning a base64 string means the whole clip is buffered first and arrives about a third larger. Piping the audio through as audio/mpeg would start playback sooner, and it would make your Cache-Control tip work nicely at the CDN too.
Was base64 a deliberate choice to keep the frontend example simple?