Remotion (Programmatic Video)
Build real MP4/GIF/WebM videos in React. Every frame is a pure function of useCurrentFrame(), so output is deterministic, scrubbable, diffable, and renderable in CI. The code-first alternative to After Effects for templated and data-driven motion graphics.
When to use
- Render MP4/GIF/WebM from code (social clips, title cards, explainers).
- Templated/data-driven videos: one composition, many outputs from props (per-user, per-record, per-row of a CSV/DB).
- Motion graphics that must be versioned, code-reviewed, and rendered in CI without a GUI.
- Programmatic audio sync, charts that animate from data, or embedding shaders/Three.js into video.
Core techniques
Frame-driven animation
Animation is derived from the current frame, never from setState or requestAnimationFrame. interpolate maps an input range to an output range; spring produces physically natural motion.
Always clamp interpolate unless an intentional overshoot is desired — by default it extrapolates linearly past the range, which produces opacity > 1 or negative values.
Scheduling: Sequence and Series
<Sequence> shifts time: children see frame reset to 0 at the sequence's from. Use it to place clips on a timeline. <Series> lays out back-to-back segments without manual offset math.
Composition registration + parametric props
The <Composition> declares id, size, fps, duration, and defaultProps. A zod schema makes props type-safe and editable in the Studio sidebar.
To make duration data-dependent, use calculateMetadata on the Composition to compute durationInFrames from props (e.g. number of rows × frames per row) before render.
Audio and beat sync
Detect beats offline (e.g. with web-audio-beat-detector or aubio) and bake the timestamps into props — never analyze audio at render time, since headless rendering has no realtime audio clock.
Rendering
Preview in the browser-based Studio; render headlessly via CLI or the programmatic API.
For batch/data-driven pipelines, render in Node with @remotion/renderer (bundle once, render many) — see the reference file.
Deliver & verify (rendered stills → MP4)
Packaged helper (
scripts/): tile your stills withscripts/contact-sheet.sh sheet.png f-hook.png f-mid.png f-end.png, then assert the encode withscripts/probe-mp4.sh out.mp4 [WxH] [fps]. Seescripts/README.md.
Remotion is frame-deterministic by construction — every frame is a pure function of useCurrentFrame(), so you can render any exact frame headlessly with no seek harness (this is the heavy-tier counterpart to a web scene's ?t=N). Use this tier when the output must be an MP4/GIF, must carry exact numbers/text, or batches from data; for a lightweight web animation, deliver standalone HTML instead.
Output contract:
- A Remotion project with the composition registered (
<Composition>+ zodschema+defaultProps), all motion frame-driven (no timers /Date.now()/Math.random()). - Deliverable = the rendered
out/*.mp4(plus the project, so the user can re-render with new props/data). - Duration data-dependent? compute it in
calculateMetadata, not by hand.
Verify loop — render stills → inspect → encode. Render single frames first (cheap, no video encode), inspect them, and encode the full video only once the frames are right.
- Use
npx remotion compositionsto read each composition'sdurationInFrames/fpsand pick the end frame. - Data-driven / batch: verify ONE representative props set via stills before batch-rendering all rows — catch a layout bug once instead of N times.
- README demo GIF for free:
npx remotion render Promo out/demo.gif --codec=gifproduces the first-screen proof clip (Direction D).
Before you finish:
npx remotion stillrenders cleanly at frame 0, mid, and last — no errors, no missing assets/fonts.- Numbers/text are exact and inside safe areas at every checked frame.
- Frame-driven only — no
Date.now()/Math.random()/ timers (determinism holds in CI). - Props are zod-typed; the shipped props render correctly (not just
defaultProps). - Full MP4 encoded and plays; (optional) GIF rendered for the README.
Quick reference
Gotchas
- Never use
Math.random(),Date.now(), or animation timers — they break determinism. Userandom(seed)from Remotion for stable per-frame randomness. - Load fonts and assets via
staticFile()and wait withdelayRender/continueRender, or fonts pop in mid-render. - Default
interpolateextrapolates — clamp it. useCurrentFrameinside a<Sequence>is local (starts at 0); useuseVideoConfig().durationInFramesfor absolute timing.
Reference files
references/api-and-patterns.md—interpolateoptions andEasing, spring config recipes, Sequence/Series scheduling,<Audio>+ beat-sync, parametricdefaultPropswith zod +calculateMetadata, CLI flags, programmatic@remotion/rendererbatch rendering, and embedding GLSL shaders / Three.js (@remotion/three).

