VideoFlowcodeGitHubTry itCoreRenderersReact Video EditorPlaygroundExamplesDocscodeGitHubTry it
Three renderers · Apache-2.0

Render anywhere.

The same VideoJSON drives all three backends. Pick the one that matches your target — or use all three in the same project.play_arrowTry it in the playgroundRead the guides
language

Browser → MP4 Blob

Render an MP4 entirely client-side. No server, no uploads.

dns

Server → MP4 file

Render headlessly from Node. Perfect for batch jobs and APIs.

play_circle

DOM → Live player

Mount a scrubbable 60 fps preview inside any element.

@videoflow/renderer-browser

Render an MP4 in the browser.

BrowserRenderer turns a VideoJSON into an MP4 Blob directly in the page — no server round-trips. Cancel mid-render with an AbortController, stream progress, and offload work to a Web Worker to keep the main thread responsive.

$ npm install @videoflow/renderer-browser

✓ Progress callback

Drive progress bars with a per-frame 0 → 1 signal.

✓ AbortController

Cancel a long export cleanly mid-flight.

✓ Worker-accelerated

Off the main thread by default — UI stays snappy.

✓ Full effect & transition parity

All 27 transitions and 42 GLSL effects render via WebGL/WebCodecs.

import BrowserRenderer from '@videoflow/renderer-browser';

const ctrl = new AbortController();

renderBtn.addEventListener('click', async () => {
  const blob = await BrowserRenderer.render(videoJSON, {
    onProgress: (p) => { progressEl.value = p; },
    signal: ctrl.signal,
    worker: true,
  });
  const url = URL.createObjectURL(blob);
  downloadBtn.href = url;
  downloadBtn.hidden = false;
});

cancelBtn.addEventListener('click', () => {
  ctrl.abort();
});
One JSON, every output

Author once. Render everywhere.

Transitions, effects, groups, captions, audio mixing — every primitive renders byte-for-byte the same across the three backends. Preview in the DOM while you author, export an MP4 in the browser when the user clicks Save, queue a deterministic server render for batch jobs.

// 1. Author once
const json = await $.compile();

// 2. Preview live in the DOM
const dom = new DomRenderer(document.querySelector('#video-preview'));
await dom.loadVideo(json);
dom.play();

// 3. Export an MP4 blob in the browser
const blob = await BrowserRenderer.render(json);

// 4. Or queue a deterministic server render
await ServerRenderer.render(json, { output: 'out.mp4' });
@videoflow/renderer-server

Render headlessly from Node.

import ServerRenderer from '@videoflow/renderer-server';

await ServerRenderer.render(videoJSON, {
  outputType: 'file',
  output: './out.mp4',
  verbose: true,
  onProgress: (p) => process.stdout.write(`\r${(p * 100).toFixed(1)}%`),
});

// → ./out.mp4 — deterministic on every machine

Point the server renderer at a VideoJSON and get an MP4 file on disk — or a Buffer you can stream. By default it encodes entirely inside headless Chromium via WebCodecs + MediaBunny — no ffmpeg dependency required. Pass { ffmpeg: true } to switch to the alternative per-frame ffmpeg pipeline when you need custom encoder flags.

$ npm install @videoflow/renderer-server

✓ No ffmpeg required

Default pipeline encodes via WebCodecs in headless Chromium. ffmpeg is opt-in.

✓ Deterministic output

The same JSON renders identically on every machine.

✓ Queue-friendly

Ideal for worker queues, scheduled jobs, and CI pipelines.

✓ Fonts & captions

Google Fonts are fetched and embedded at render time.

✓ File or Buffer output

Write to disk, or stream the MP4 to S3 / your bucket of choice.

@videoflow/renderer-dom

A live player for preview & scrubbing.

DomRenderer mounts a scrubbable, 60 fps live player inside any host element using a Shadow DOM root for style isolation. Ideal for previews, dashboards, or any interactive experience.

$ npm install @videoflow/renderer-dom

✓ Shadow DOM isolation

No style leaks in or out.

✓ Frame-accurate scrub

renderFrame(n) jumps precisely to any frame.

✓ Audio sync

Audio plays via an HTML element and stays in lock-step with frames.

✓ Powers the editor

The same renderer drives the live preview in @videoflow/react-video-editor.

import DomRenderer from '@videoflow/renderer-dom';

const renderer = new DomRenderer(document.querySelector('#video-preview'));
await renderer.loadVideo(videoJSON);
renderer.play();

// scrub: renderer.renderFrame(120);
// pause: renderer.pause();
Same JSON. Same output.

Two flows, three render targets.

The flows below are authored once and rendered through every backend. Click </> on any showcase to see the source.

Transitions render identically across browser, server, and DOM.GLSL effects compile to the same WebGL pipeline on every backend.Keyframes are frame-perfect — server output matches the live preview.
Pick your target

Which renderer should I use?

Use caseRendererWhy
User clicks Export in a SaaS apprenderer-browserNo server cost, no upload, MP4 ready instantly.
Batch render 10k videos overnightrenderer-serverRun on a queue, deterministic, no ffmpeg dependency by default.
API endpoint that returns an MP4renderer-serverStreams a Buffer / writes a file on the host.
Live preview in an editor or dashboardrenderer-dom60 fps scrubbing with frame-accurate seeking.
LLM-authored video in an agent looprenderer-serverJSON in, MP4 out, fully headless.
Embed a video player on a marketing pagerenderer-domDrop-in <Shadow DOM> player with no style leaks.

Pick a backend and ship.

Browser guideServer guideDOM guide
VideoFlow

Open-source toolkit for composing videos from code.

Product

CoreRenderersReact Video EditorPlayground

Learn

DocsAPI referenceExamplesvs. Remotionvs. FFmpeg

Project

GitHubLicenseContact

Legal

TermsPrivacy
© 2026 VideoFlow. Apache-2.0 core.