lobe-tts: text to speech for Node, the browser and React
🎤 Lobe TTS - A high-quality & reliable TTS/STT library for Server and Browser
At a glance
- What is it?
- lobe-tts is the TypeScript toolkit the Lobe Chat team extracted from its own application, offering a uniform interface over EdgeSpeechTTS, MicrosoftTTS, OpenAITTS and OpenAISTT, plus React hooks and audio components with a visualiser. It is ESM only, needs a WebSocket polyfill on Node, and reached a v6 major release in October 2026.
- Who is it for?
- lobe-tts fits a team that already ships a chat interface and wants spoken output without building a synthesis pipeline from scratch, especially one that runs in both a server process and a browser tab, and one that wants React components for playback and visualisation rather than a raw audio blob API. It does not fit a CommonJS project, since the package is ESM only, and it does not fit a Node service that has not accounted for the missing WebSocket global.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 1 day ago.
- What is it written in?
- Mainly TypeScript, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.
Editorial analysis
A toolkit extracted from a chat application
The origin story is the most useful thing about lobe-tts, because it explains the shape of the API. Lobe Chat supports text to speech and speech to text, turning text messages into voice output so users can talk to the conversational agent as if a person were answering, with a choice of voices.
While building that, the team found there was no satisfactory TTS frontend library on the market. So they built one, and in the process handled data conversion, audio progress management, and speech visualisation. The decision to refine that implementation and open source it is stated as a note in the README, and the package is described as a high quality TTS toolkit written in TypeScript that works on the server side and in the browser.
That origin explains the shape of the library better than a feature list would. The server side is about producing an audio file, and the browser side is about the awkward part of any chat application, which is that audio has to start, stop, show progress, and look like something rather than like a blinking download link.
The package is @lobehub/tts, published under the MIT licence, with a documentation site at tts.lobehub.com, a changelog in the repository, and the version currently at 6.0.0.
The engines it fronts are named in the README: EdgeSpeechTTS, MicrosoftTTS, OpenAITTS, and OpenAISTT. The first two are speech synthesis, the third is speech synthesis, and the fourth is speech recognition, so the library covers both directions of the pipeline.
Fifteen lines on the server, and the WebSocket polyfill
The server example is the one to start with, and it shows the whole flow: instantiate an engine with a locale, build a payload with the input text and the voice, call create, and write the result to a file.
// index.js
import { EdgeSpeechTTS } from '@lobehub/tts';
import { Buffer } from 'buffer';
import fs from 'fs';
import path from 'path';
// Instantiate EdgeSpeechTTS
const tts = new EdgeSpeechTTS({ locale: 'en-US' });
// Create speech synthesis request payload
const payload = {
input: 'This is a speech demonstration',
options: {
voice: 'en-US-GuyNeural',
},
};
// Call create method to synthesize speech
const response = await tts.create(payload);
// generate speech file
const mp3Buffer = Buffer.from(await response.arrayBuffer());
const speechFile = path.resolve('./speech.mp3');
fs.writeFileSync(speechFile, mp3Buffer);The shape of the response is worth noting, because it decides how you integrate this. The call returns something with an arrayBuffer method, so the engine response is a web style response object rather than a Node stream or a Buffer. That is convenient in a browser, and in a server it means one conversion step, which the example performs with Buffer.from.
The script is run with Bun in the README, using `bun index.js`, and the payload itself is plain data: an input string and an options object carrying the voice name, here a neural voice for the en-US locale.
Then there is the catch that will stop a Node server on its first run. The Node.js environment lacks the WebSocket instance, so the library needs a polyfill, and the README shows exactly two lines to add at the top of the file.
// import at the top of the file
import WebSocket from 'ws';
global.WebSocket = WebSocket;This is not a stylistic preference inside the library, it is a runtime requirement of the environment, and the patch is global. If your service runs several copies of the library in one process, this assignment is the thing that has to happen before any of them works.
The browser side is hooks and a visualiser
The React side of the package is imported from a separate entry point, @lobehub/tts/react, and it exports three things: a useAudioPlayer hook, an AudioPlayer component, and an AudioVisualizer.
import { AudioPlayer, AudioVisualizer, useAudioPlayer } from '@lobehub/tts/react';
export default () => {
const { ref, isLoading, ...audio } = useAudioPlayer(url);
return (
<Flexbox align={'center'} gap={8}>
<AudioPlayer audio={audio} isLoading={isLoading} style={{ width: '100%' }} />
<AudioVisualizer audioRef={ref} isLoading={isLoading} />
</Flexbox>
);
};The hook takes a URL and returns a ref, a loading flag, and the rest of the player state, which is then spread into the AudioPlayer as an audio prop. The visualiser takes the ref and the loading flag, which is how a frequency or waveform display can follow the same audio element.
The README describes the browser side as providing high quality React hooks and visual audio components supporting loading, playing, pausing, and dragging the timeline, plus a rich set of capabilities for adjusting the audio track styles. That list is the difference between this and a bare audio tag: dragging the timeline and the style controls are the parts an application team would otherwise build per project.
Note the shape of the state, because it constrains your components. The hook returns a ref separately from the rest of the state, so the player controls and the visualiser share the same underlying element through that ref rather than through a shared context, and the isLoading flag is threaded into both components explicitly.
ESM only, and what that costs a Next.js project
The package is ESM only, and that is stated as an important note above the installation instructions. The install itself is a single command, and the README offers both a pnpm and a bun invocation.
$ pnpm i @lobehub/tts$ bun add @lobehub/ttsThe ESM only constraint is the one that bites. A CommonJS application, or a build pipeline that does not understand ESM, cannot require this package, and the usual fix, transpiling it away, is exactly what the Next.js section is about.
For a Next.js project, server side rendering has to be handled explicitly, and the README says to add transpilePackages with the package name to next.config.js.
const nextConfig = {
transpilePackages: ['@lobehub/tts'],
};That is the whole Next.js integration: one line in the config, and a note that the reason is working correctly with Next.js SSR. If your framework has no equivalent of transpilePackages, you are the one who has to solve the ESM problem, and the README does not describe a CommonJS build of the package.
The package manifest is conventional and worth a look because it explains the shape of the public API. There are three export paths: the root, the react path, and a server path, each with its own type declarations and ESM build. The published files are the es directory, and the manifest declares sideEffects as false, which is the flag that lets a bundler drop unused imports. The build is done with tsdown, the tests with vitest, and the release with semantic-release.
Three export paths and a repository built around automated releases
The package manifest tells you how the library is meant to be consumed. The root export carries the engine classes, the react export carries the hooks and components, and there is a server export as well, so a bundler can pull the server side without dragging the browser code along.
$ git clone https://github.com/lobehub/lobe-tts.git
$ cd lobe-tts
$ bun install
$ bun devThe repository is a monorepo shaped workspace with an api directory, a docs directory, a lib directory, a public directory, a src directory, and a tests directory, plus a tsdown config and a vitest config. The example folder contains a text to speech on server example.
The release engineering is where the effort shows. The scripts include lint with eslint over src, api, and lib, a separate circular dependency check with dpdm that fails on circular imports, a markdown lint with remark, a style lint with stylelint, a prettier pass, a husky hook installation in the prepare step, a clean-package step before and after packing, and a release script that runs semantic-release. Renovate handles dependency updates, and the changelog is generated through a changelog configuration file. There is also an i18n script for translating markdown, which fits a project that ships a documentation site with a Chinese and English presence.
The cadence is fast. v5.2.1 was published on 2026-10-07, a v6.0.0 beta on 2026-10-10, and v6.0.0 on 2026-10-15, with the last push on 2026-10-15. A major version landing inside a week of a patch release means you should read the changelog before upgrading rather than assume the change is additive.
Where the abstraction stops
The library gives you a uniform interface, but the engines behind it are not uniform, and the differences show up in the operations.
The engines are third party services, Edge, Microsoft, and OpenAI, so each one has its own authentication, its own quotas, and its own terms of use, and the library does not abstract those away. The payload in the example carries a voice name, which means the set of valid voice identifiers differs per engine, so a voice string is not portable between them even though the call signature is.
The documented surface is also narrower than a name like a TTS toolkit suggests. The example shows a payload with an input and an options object holding a voice, and the README does not document the full option set, the available voices per locale, or the audio formats each engine can return. The locale is given at construction time, in the example en-US, which ties the engine instance to one market.
On the browser side, the hook takes a URL, not a stream or an audio buffer, so the component expects audio that is already addressable, which means the server side or a blob URL has to produce it first. The library removes the awkward part of playback and visualisation, not the delivery problem.
The Node.js WebSocket requirement is the other sharp edge. It is a global patch of the runtime, it is not optional on Node, and a service that must support older Node versions or a runtime without the ws package installed will not start.
None of that makes the library a poor fit for its purpose. It is a well packaged front end for a specific set of engines, extracted from a working application, with a documentation site, a changelog, an example project, and a release pipeline that ships on a schedule. Read it as that, and pick the engine whose quirks you are prepared to live with.
Editorial conclusion
lobe-tts fits a team that already ships a chat interface and wants spoken output without building a synthesis pipeline from scratch, especially one that runs in both a server process and a browser tab, and one that wants React components for playback and visualisation rather than a raw audio blob API. It does not fit a CommonJS project, since the package is ESM only, and it does not fit a Node service that has not accounted for the missing WebSocket global. Verify first that the engine you plan to use, Edge, Microsoft, or OpenAI, is one of the four the library exposes, that your Node runtime has the ws polyfill wired in before the first call, and that your bundler can handle the package, since Next.js needs transpilePackages set to @lobehub/tts in next.config.js.
Frequently asked questions
How do I install the lobe-tts package?
The package is @lobehub/tts and it is ESM only. The README gives both a pnpm install and a bun add command, and for local development it suggests cloning the repository and running bun install followed by bun dev, or using GitHub Codespaces.
Which text to speech engines does lobe-tts support?
The README names four: EdgeSpeechTTS, MicrosoftTTS, OpenAITTS, and OpenAISTT, the last of which covers speech to text. The engines are instantiated with a locale, for example en-US, and the request payload carries the input text and a voice in its options.
Why does lobe-tts need a WebSocket polyfill on Node.js?
The Node.js environment lacks the WebSocket instance, so the README instructs you to import the ws package and assign it to global.WebSocket before using the library. It is a runtime requirement on Node rather than an optional extra.
What do the React components in lobe-tts provide?
The react export provides a useAudioPlayer hook plus AudioPlayer and AudioVisualizer components. The hook returns a ref, a loading flag and the player state, and the README describes support for loading, playing, pausing, dragging the timeline, and adjusting the audio track styles.
How do I use lobe-tts with Next.js?
The README says that to work correctly with Next.js SSR you add transpilePackages with the @lobehub/tts entry to next.config.js, since the package is ESM only. The example shows the config with a transpilePackages array containing the package name.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/lobehub-lobe-tts)Was this useful?