Rendering on a server
@videoflow/renderer-server runs in Node. It launches headless Chromium via Playwright, loads the same browser renderer the browser package uses, and exports an MP4 — by default entirely inside the browser (WebCodecs + MediaBunny mux), with no ffmpeg dependency. An alternative ffmpeg pipeline is also available for hosts that need custom encoder flags.
Install
npm install @videoflow/core @videoflow/renderer-server
npx playwright install chromiumNode 18+ is required. ffmpeg is optional — only install it system-wide if you plan to pass { ffmpeg: true }.
To a file
import ServerRenderer from '@videoflow/renderer-server';
import VideoFlow from '@videoflow/core';
const $ = new VideoFlow({ width: 1920, height: 1080, fps: 30 });
// ... build flow ...
const json = await $.compile();
await ServerRenderer.render(json, {
outputType: 'file',
output: './out.mp4',
onProgress: (p) => console.log(`${Math.round(p * 100)}%`),
});To a buffer
const buffer = await ServerRenderer.render(json, { outputType: 'buffer' });
// e.g. upload to S3 straight from memory
await s3.send(new PutObjectCommand({ Bucket, Key, Body: buffer, ContentType: 'video/mp4' }));Options
| Option | Default | Notes |
|---|---|---|
ffmpeg | false | When true, switches to the alternative per-frame screenshot → ffmpeg pipeline. Requires ffmpeg 4.4+ on PATH. |
outputType | 'buffer' | 'file' | 'buffer'. |
output | — | Path when outputType: 'file'. |
signal | — | AbortSignal for cancellation. |
onProgress | — | (p: 0..1) => void. |
verbose | false | Stream stage logs to the console. |
Pipelines
| Browser export (default) | ffmpeg pipeline | |
|---|---|---|
| Encoder | WebCodecs (H.264) + MediaBunny mux | JPEG screenshots → libx264 via ffmpeg |
| Audio | Encoded inside the browser, muxed with video | Rendered to WAV, muxed by ffmpeg |
| System deps | Playwright Chromium only | Playwright + ffmpeg 4.4+ |
| Speed | Several × faster — no per-frame round-trip | Slower; useful when you need ffmpeg flags |
| Output | MP4 (H.264 + AAC) | MP4 (configurable via ffmpeg) |
Single frame and audio
Need a thumbnail or a music-only export? The renderer exposes per-frame and audio-only methods on the instance:
const renderer = new ServerRenderer(json);
const jpeg = await renderer.renderFrame(120); // Buffer (JPEG)
const wav = await renderer.renderAudio(); // Buffer (WAV) | null
await renderer.cleanup();Inside an HTTP handler
// Express / Node / Bun
app.post('/render', async (req, res) => {
const json = req.body;
try {
const buffer = await ServerRenderer.render(json, { outputType: 'buffer' });
res.setHeader('Content-Type', 'video/mp4');
res.setHeader('Content-Disposition', 'attachment; filename="video.mp4"');
res.end(buffer);
} catch (err) {
res.status(500).json({ error: String(err) });
}
});closeSharedBrowser() on shutdown — the package keeps a single Chromium instance alive across renders.