DEV Community

VoiceDeveloper
VoiceDeveloper

Posted on

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

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
Enter fullscreen mode Exit fullscreen mode

1. Grab Your ElevenLabs API Key

  1. Visit the ElevenLabs dashboard (via the affiliate link above).
  2. Create a new project or use the default one.
  3. 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
Enter fullscreen mode Exit fullscreen mode

Never commit this file; Next.js automatically excludes .env.local from 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',
    },
  });
}
Enter fullscreen mode Exit fullscreen mode

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>
  );
}
Enter fullscreen mode Exit fullscreen mode

Add the component to any page, for example app/page.tsx:

import VoiceSynthesizer from '@/components/VoiceSynthesizer';

export default function Home() {
  return (
    <main>
      <VoiceSynthesizer />
    </main>
  );
}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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:

  1. Add the environment variable ELEVENLABS_API_KEY in the Vercel dashboard.
  2. 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"
}
Enter fullscreen mode Exit fullscreen mode

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)