Dev.to WebDev 🛠 Dev 👁 0 📖 4 min read

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‑wor

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

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

touch tsconfig.json

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"]
}

3. Store your ElevenLabs key

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

ELEVENLABS_API_KEY=sk_your_api_key_here

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);
}

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

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

const DEFAULT_VOICE_ID = 'EXAVITQu4vr4xnSDxMaL';

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>
  );
}

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"

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!

📰 Read the original article on Dev.to WebDev

Originally published by Dev.to WebDev. Aggregated on AIWithGhost for educational purposes — full credit and traffic to the original publisher.