| name | ref-elevenlabs |
| description | Reference for ElevenLabs Text-to-Speech JavaScript SDK. Covers streaming TTS, voice selection, and Next.js API route integration. Consult when implementing problem narration or voice features. |
ElevenLabs TTS Reference
Packages
bun add elevenlabs
JavaScript SDK: elevenlabs (v1.59.0 installed in this project)
Environment Variables
ELEVENLABS_API_KEY=your_api_key_here
CRITICAL: Build-Time Gotcha
DO NOT create ElevenLabsClient at module scope in Next.js. The constructor throws
if ELEVENLABS_API_KEY is not set, and Next.js evaluates module-scope code during
bun run build ("Collecting page data" phase). Always lazy-init:
let _client: ElevenLabsClient | null = null;
function getClient() {
if (!_client) {
_client = new ElevenLabsClient({ apiKey: process.env.ELEVENLABS_API_KEY });
}
return _client;
}
Also add export const dynamic = "force-dynamic"; to any route using ElevenLabs.
Basic Usage (TypeScript/Node)
Non-Streaming (generate full audio)
import { ElevenLabsClient } from "elevenlabs";
const client = new ElevenLabsClient({
apiKey: process.env.ELEVENLABS_API_KEY,
});
const audio = await client.textToSpeech.convert("pNInz6obpgDQGcFmaJgB", {
text: "Hello, welcome to CodeGym!",
model_id: "eleven_flash_v2_5",
output_format: "mp3_22050_32",
});
Streaming TTS
IMPORTANT: The method is convertAsStream, NOT stream. The .stream() method
does not exist on the TextToSpeech class in v1.59.0.
const audioStream = await client.textToSpeech.convertAsStream("pNInz6obpgDQGcFmaJgB", {
text: "This problem is about Express middleware...",
model_id: "eleven_flash_v2_5",
output_format: "mp3_22050_32",
voice_settings: {
stability: 0.5,
similarity_boost: 0.75,
speed: 1.0,
},
});
for await (const chunk of audioStream) {
}
Next.js API Route (app/api/tts/route.ts)
import { ElevenLabsClient } from "elevenlabs";
import { NextResponse } from "next/server";
export const dynamic = "force-dynamic";
let _client: ElevenLabsClient | null = null;
function getClient() {
if (!_client) {
_client = new ElevenLabsClient({ apiKey: process.env.ELEVENLABS_API_KEY });
}
return _client;
}
export async function POST(req: Request) {
const { text } = await req.json();
const audioStream = await getClient().textToSpeech.convertAsStream(
"pNInz6obpgDQGcFmaJgB",
{
text,
model_id: "eleven_flash_v2_5",
}
);
stream = ({
() {
( chunk audioStream) {
controller.(chunk);
}
controller.();
},
});
(stream, {
: {
: ,
: ,
},
});
}
Client-Side Playback
async function playNarration(text: string) {
const res = await fetch("/api/tts", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ text }),
});
const blob = await res.blob();
const url = URL.createObjectURL(blob);
const audio = new Audio(url);
audio.play();
}
Pre-Made Voice IDs
| Voice | ID | Style |
|---|
| Adam | pNInz6obpgDQGcFmaJgB | Deep, narration |
| Rachel | 21m00Tcm4TlvDq8ikWAM | Calm, clear |
| Domi | AZnzlk1XvdvUeBnXmlld | Strong, bold |
Models
| Model | ID | Use Case |
|---|
| Flash v2.5 | eleven_flash_v2_5 | Fastest, lowest latency — use this |
| Multilingual v2 | eleven_multilingual_v2 | Best quality, higher latency |
| Turbo v2.5 | eleven_turbo_v2_5 | Balance of speed + quality |
Gotchas
convertAsStream() NOT stream() — v1.59.0 does not have .stream(). Use .convertAsStream() for streaming, .convert() for full audio. Confirmed by reading node_modules/elevenlabs/api/resources/textToSpeech/client/Client.d.ts.
- Available methods on
TextToSpeech: convert(), convertWithTimestamps(), convertAsStream(), streamWithTimestamps() — that's it.
- Module-scope client init BREAKS builds —
new ElevenLabsClient() throws if env var is missing. Next.js evaluates module scope during bun run build. Always lazy-init inside a getter function.
- Add
export const dynamic = "force-dynamic" to any route using ElevenLabs.
- First arg to
.convert() and .convertAsStream() is voice_id — not an options object
eleven_flash_v2_5 is best for hackathon — lowest latency, good quality
- Audio format:
mp3_22050_32 is small + fast; mp3_44100_128 for higher quality
- Streaming response — must convert Node.js Readable to Web ReadableStream for Next.js Response
- Don't call from client — always proxy through API route to protect API key