Skip to content

Rendering on a server

@ilovevideoeditor/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

bash
npm install @ilovevideoeditor/core @ilovevideoeditor/renderer-server
npx playwright install chromium

Node 18+ is required. ffmpeg is optional — only install it system-wide if you plan to pass { ffmpeg: true }.

To a file

javascript
import ServerRenderer from '@ilovevideoeditor/renderer-server';
import iLoveVideoEditor from '@ilovevideoeditor/core';

const $ = new iLoveVideoEditor({ 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

javascript
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

OptionDefaultNotes
ffmpegfalseWhen true, switches to the alternative per-frame screenshot → ffmpeg pipeline. Requires ffmpeg 4.4+ on PATH.
outputType'buffer'`'file'
outputPath when outputType: 'file'.
signalAbortSignal for cancellation.
onProgress(p: 0..1) => void.
verbosefalseStream stage logs to the console.

Pipelines

Browser export (default)ffmpeg pipeline
EncoderWebCodecs (H.264) + MediaBunny muxJPEG screenshots → libx264 via ffmpeg
AudioEncoded inside the browser, muxed with videoRendered to WAV, muxed by ffmpeg
System depsPlaywright Chromium onlyPlaywright + ffmpeg 4.4+
SpeedSeveral × faster — no per-frame round-tripSlower; useful when you need ffmpeg flags
OutputMP4 (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:

javascript
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

javascript
// 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) });
  }
});

NOTE

Scaling. Render jobs are CPU-heavy. In production, push them into a queue (BullMQ, SQS, Temporal) and cap concurrency to your CPU count. Call closeSharedBrowser() on shutdown — the package keeps a single Chromium instance alive across renders.

Released under the MIT License.