DEV Community

VoiceDeveloper
VoiceDeveloper

Posted on

Integrating ElevenLabs with Next.js: Step-by-Step Guide

1. Why ElevenLabs for Next.js?

If you’re building a web app that needs dynamic, high‑quality speech, you’ll quickly realize that most open‑source TTS engines feel a bit stale or lack the natural prosody that a real‑world user expects. ElevenLabs offers a cloud‑based API that delivers:

  • Real‑time, neural‑network‑generated voice
  • Voice cloning with just a few minutes of audio
  • Low‑latency, REST‑friendly endpoints

In short, it’s the fastest way to add lifelike voice to a Next.js site without wrestling with heavy audio processing libraries.

Want to see it in action? Grab your free API key through this link: https://try.elevenlabs.io/kr07zfuqn1bp


2. Project setup

npx create-next-app elevenlabs-demo
cd elevenlabs-demo
npm install
Enter fullscreen mode Exit fullscreen mode

We’ll use TypeScript for type safety, but the concepts apply to plain JavaScript as well.

touch tsconfig.json
Enter fullscreen mode Exit fullscreen mode

Add a minimal tsconfig.json:

{
  "compilerOptions": {
    "target": "es5",
    "lib": ["dom", "dom.iterable", "esnext"],
    "allowJs": true,
    "skipLibCheck": true,
    "strict": true,
    "forceConsistentCasingInFileNames": true,
    "module": "esnext",
    "moduleResolution": "node",
    "resolveJsonModule": true,
    "isolatedModules": true,
    "noEmit": true,
    "jsx": "preserve"
  },
  "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
  "exclude": ["node_modules"]
}
Enter fullscreen mode Exit fullscreen mode

3. Store your ElevenLabs key

Create a .env.local file in the project root:

ELEVENLABS_API_KEY=sk_your_api_key_here
Enter fullscreen mode Exit fullscreen mode

Next.js automatically loads .env.local, so you can reference it in your code via process.env.ELEVENLABS_API_KEY.

Remember, never commit .env.local to version control.


4. Build an API route that talks to ElevenLabs

Next.js API routes give us a clean place to keep your secret key out of the client bundle.

// pages/api/synthesize.ts
import type { NextApiRequest, NextApiResponse } from 'next';

export default async function handler(
  req: NextApiRequest,
  res: NextApiResponse
) {
  const { text, voiceId } = req.body;

  if (!text || !voiceId) {
    return res.status(400).json({ error: 'Missing text or voiceId' });
  }

  const apiKey = process.env.ELEVENLABS_API_KEY;
  if (!apiKey) {
    return res.status(500).json({ error: 'API key missing' });
  }

  const response = await fetch(
    `https://api.elevenlabs.io/v1/text-to-speech/${voiceId}`,
    {
      method: 'POST',
      headers: {
        'xi-api-key': apiKey,
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({
        text,
        voice_settings: {
          stability: 0.75,
          similarity_boost: 0.75,
        },
      }),
    }
  );

  if (!response.ok) {
    const error = await response.text();
    return res.status(response.status).json({ error });
  }

  // ElevenLabs returns the audio as a binary stream
  const audioBuffer = await response.arrayBuffer();
  const buffer = Buffer.from(audioBuffer);

  // Return the audio as a blob with proper headers
  res.setHeader('Content-Type', 'audio/mpeg');
  res.setHeader('Content-Length', buffer.length.toString());
  res.send(buffer);
}
Enter fullscreen mode Exit fullscreen mode

What’s happening here?

  1. Validate input – we expect text and voiceId.
  2. Call ElevenLabs – use the official REST endpoint.
  3. Stream back audio – the client can directly consume it.

5. Get a voice ID

ElevenLabs requires you to pick a voice ID. You can fetch available voices via their API:

curl -H "xi-api-key: $ELEVENLABS_API_KEY" \
     https://api.elevenlabs.io/v1/voices
Enter fullscreen mode Exit fullscreen mode

Pick an ID (e.g., EXAVITQu4vr4xnSDxMaL) and store it in a constant:

const DEFAULT_VOICE_ID = 'EXAVITQu4vr4xnSDxMaL';
Enter fullscreen mode Exit fullscreen mode

6. Build the UI

A simple form that sends the text to the API and plays the resulting audio:

// pages/index.tsx
import { useState, useRef } from 'react';

export default function Home() {
  const [text, setText] = useState('');
  const [loading, setLoading] = useState(false);
  const audioRef = useRef<HTMLAudioElement>(null);

  const handleSpeak = async () => {
    setLoading(true);

    const res = await fetch('/api/synthesize', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ text, voiceId: DEFAULT_VOICE_ID }),
    });

    if (res.ok) {
      const blob = await res.blob();
      const url = URL.createObjectURL(blob);
      if (audioRef.current) {
        audioRef.current.src = url;
        audioRef.current.play();
      }
    } else {
      const err = await res.text();
      alert(`Error: ${err}`);
    }

    setLoading(false);
  };

  return (
    <div style={{ padding: '2rem', fontFamily: 'sans-serif' }}>
      <h1>ElevenLabs + Next.js Demo</h1>
      <textarea
        rows={4}
        cols={60}
        value={text}
        onChange={(e) => setText(e.target.value)}
        placeholder="Type something to speak…"
        style={{ width: '100%', marginBottom: '1rem' }}
      />
      <br />
      <button onClick={handleSpeak} disabled={loading}>
        {loading ? 'Generating…' : 'Speak'}
      </button>
      <audio ref={audioRef} controls style={{ marginTop: '1rem' }} />
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

How it works

  1. User types text – stored in text.
  2. Click “Speak” – triggers handleSpeak.
  3. API call – sends the text to our serverless endpoint.
  4. Play audio – the binary response is turned into a Blob URL and fed to an <audio> element.

7. Voice cloning (optional but powerful)

If you want a custom voice, ElevenLabs lets you upload a short clip (≤ 5 min) and generate a new voice ID. Here’s a quick snippet to upload a clip:

curl -X POST https://api.elevenlabs.io/v1/voices \
  -H "xi-api-key: $ELEVENLABS_API_KEY" \
  -F "name=My Custom Voice" \
  -F "file=@/path/to/clip.wav"
Enter fullscreen mode Exit fullscreen mode

The response will include a new voice_id you can reuse in the synth endpoint. Once you have that, swap DEFAULT_VOICE_ID with your custom ID.


8. Deployment tips

  • Serverless functions: The API route above works out‑of‑the‑box on Vercel, Netlify, or any platform that supports Next.js.
  • Environment variables: Store ELEVENLABS_API_KEY securely in your deployment provider’s secret store.
  • CORS: Since the API route is on the same domain, you don’t need extra CORS headers.

9. Performance & cost

  • Latency: ElevenLabs’ endpoint is low‑latency; most requests finish in < 300 ms.
  • Pricing: They offer a free tier (limited characters per month). Beyond that, pricing is per‑character, which is competitive for most small‑to‑medium projects.

10. Final thoughts

Integrating ElevenLabs into a Next.js app is a breeze: one API route, a tiny form, and you’re ready to deliver rich, human‑like voice. Whether you’re building a voice‑enabled chatbot, an audiobook generator, or a voice‑clone demo, ElevenLabs gives you the audio fidelity you’d expect from a commercial product.

Ready to add voice to your Next.js project? Grab your free API key and start experimenting: https://try.elevenlabs.io/kr07zfuqn1bp

Happy coding, and may your apps sound amazing!

Top comments (0)