Remotion lets you create real MP4 videos using React. You write components with JSX, animate them as a function of the current frame, preview everything live in a browser-based Studio, and render the result to a video file — locally, on a server, or in the cloud. If you can build a web page, you can build a video.
This guide is written for the people who use Remotion: motion designers, developers, and teams who want to generate videos programmatically. It covers installation, every core building block, the Studio, rendering, embedding videos in your own app, and the large ecosystem of helper packages. Architecture, deployment internals, and operational concerns live in the companion Admin Guide.
Note
Remotion is React 19-ready and ships as the
remotionpackage (currently4.0.472) plus a family of@remotion/*packages. Everything in this guide is derived directly from the source code of those packages.
Table of Contents
- What Remotion Is
- Quick Start
- Installation & Requirements
- Core Concepts
- Defining Compositions
- Animating
- Sequencing & Timing
- Working with Media
- Loading Data & Asynchronous Work
- The Remotion Studio
- Rendering Videos
- Embedding Videos with the Player
- The Package Ecosystem
- Cloud & Server Rendering
- Common Workflows
- Troubleshooting
- FAQ
- Licensing
- Command & Reference Tables
1. What Remotion Is
Remotion turns React into a video engine. The mental model is simple:
- A video is a sequence of frames.
- At any moment, the current frame is just a number.
- Your React component renders what the video should look like at that frame.
- To play or render, Remotion advances the frame number and captures each frame.
Because every frame is a pure function of its frame number, your animations are deterministic: the same frame always produces the same pixels, whether you preview it, render it locally, or render it across a thousand cloud workers.
frame 0 frame 15 frame 30 frame 45
│ │ │ │
▼ ▼ ▼ ▼
[render] [render] [render] [render] → stitched into video.mp4
You get the full power of the web platform — CSS, SVG, Canvas, WebGL, fonts, the DOM — plus React's component model for composition and reuse.
Tip
If you are new, the fastest way to understand Remotion is to scaffold a project (next section), open the Studio, and scrub the timeline while watching the code update.
2. Quick Start
The fastest path from nothing to a playing video is the project scaffolder.
npm
npx create-video@latest
pnpm
pnpm create video
yarn
yarn create video
bun
bun create video
This launches an interactive wizard that lets you pick a template (Hello World, blank, audiogram, TikTok-style captions, Next.js, Remotion Recorder, and more), then installs dependencies.
Once it finishes:
cd my-video
npx remotion studio
Server ready - Local: http://localhost:3000
The Studio opens in your browser. You'll see a sidebar of compositions, a live preview, and a timeline you can scrub. Edit a file in src/, save, and the preview hot-reloads.
To render the default composition to a file:
npx remotion render
+ Bundled 100%
+ Rendered 100% out/HelloWorld.mp4
That's the whole loop: scaffold → studio → edit → render.
3. Installation & Requirements
Adding Remotion to an existing project
Remotion is a set of npm packages. The core package is remotion; you almost always also want the CLI:
npm i remotion @remotion/cli
To render programmatically (instead of the CLI), add the renderer and bundler:
npm i @remotion/renderer @remotion/bundler
Important
All
remotionand@remotion/*packages must be on the exact same version. Mixing versions causes hard-to-debug errors. When installing, pin exact versions (remove the^), e.g.npm i remotion --save-exact. Remotion warns at runtime if it detects multiple versions.
Peer dependencies
The remotion package declares React as a peer dependency:
| Peer | Required version |
|---|---|
react |
>=16.8.0 (React 19 supported and recommended) |
react-dom |
>=16.8.0 |
System requirements
| Requirement | Detail |
|---|---|
| Node.js | Node 18+ (or Bun). The Studio and renderer run on Node/Bun. |
| Chrome/Chromium | Used for rendering. Remotion downloads its own Chrome Headless Shell automatically — you don't need a system Chrome. |
| FFmpeg | Bundled with @remotion/renderer. You do not install FFmpeg separately. |
| OS | macOS, Windows, and Linux are supported. On Linux, some shared libraries may be needed for headless Chrome (see the Admin Guide). |
| Disk | Renders write frames to a temp directory before stitching; ensure free space for long videos. |
Note
Because Chrome and FFmpeg are managed by Remotion itself, a fresh
npm iis usually all you need on a developer machine. Server and Docker setups have additional library requirements covered in the Admin Guide.
Project anatomy
A typical Remotion project looks like this:
my-video/
├── package.json
├── remotion.config.ts # Studio/render configuration (Config.* calls)
├── public/ # Static assets — referenced via staticFile()
└── src/
├── index.ts # Calls registerRoot(RemotionRoot)
├── Root.tsx # Lists your <Composition>s
└── MyComp.tsx # An actual video component
The entry point (src/index.ts) registers the root component:
import {registerRoot} from 'remotion';
import {RemotionRoot} from './Root';
registerRoot(RemotionRoot);
4. Core Concepts
Before building, internalize these five ideas. Everything else composes from them.
Frames and FPS
A composition has a frame rate (fps) and a length (durationInFrames). A 5-second video at 30 fps is durationInFrames={150}. You convert between seconds and frames yourself: seconds * fps.
useCurrentFrame()
The single most important hook. It returns the frame currently being displayed or rendered, starting at 0.
import {useCurrentFrame} from 'remotion';
const frame = useCurrentFrame(); // 0, 1, 2, ...
Every animation derives from this number.
Important
Do not use
setInterval,requestAnimationFrame, CSS animations/transitions, orDate.now()to animate in Remotion. Those are wall-clock based and are not deterministic across frames — they will not render correctly. Drive all motion fromuseCurrentFrame().
useVideoConfig()
Returns the metadata of the composition you're inside:
import {useVideoConfig} from 'remotion';
const {width, height, fps, durationInFrames} = useVideoConfig();
Compositions
A <Composition> registers a renderable video: an id, dimensions, fps, duration, and the component to render. Compositions appear in the Studio sidebar and are the targets you render from the CLI.
The Root
registerRoot() tells Remotion which component lists your compositions. That component returns one or more <Composition> (and optionally <Folder>) elements.
import {Composition} from 'remotion';
import {MyComp} from './MyComp';
export const RemotionRoot = () => {
return (
<Composition
id="MyComp"
component={MyComp}
durationInFrames={150}
fps={30}
width={1920}
height={1080}
/>
);
};
5. Defining Compositions
<Composition>
Registers a video so it shows in the Studio and can be rendered.
| Prop | Type | Required | Description |
|---|---|---|---|
id |
string |
Yes | URL-safe unique identifier; the render target and Studio entry name. |
component |
component | Yes* | The React component to render. |
lazyComponent |
() => Promise<{default}> |
Yes* | Lazy alternative to component (code-splitting). |
durationInFrames |
number |
Yes** | Length of the video in frames. |
fps |
number |
Yes** | Frames per second. |
width |
number |
Yes** | Pixel width. |
height |
number |
Yes** | Pixel height. |
defaultProps |
object | No | Default props passed to the component (editable in the Studio). |
schema |
Zod object | No | A Zod schema used to validate and edit props visually in the Studio. |
calculateMetadata |
function | No | Compute dimensions/fps/duration/props at runtime (e.g. from a video file or API). |
* Provide exactly one of component or lazyComponent.
** Required unless a calculateMetadata function supplies them.
<Composition
id="Promo"
component={Promo}
durationInFrames={300}
fps={30}
width={1080}
height={1080}
defaultProps={{title: 'Hello'}}
/>
Tip
idmust be URL-safe — it becomes part of routes and output filenames. Stick to letters, numbers, and dashes.
calculateMetadata — dynamic durations
Often the duration depends on data: the length of an audio file, a fetched dataset, or props. calculateMetadata runs before rendering and can return any of durationInFrames, fps, width, height, props, and default render settings.
import type {CalculateMetadataFunction} from 'remotion';
const calc: CalculateMetadataFunction<{src: string}> = async ({props}) => {
const durationInSeconds = await fetchAudioDuration(props.src);
return {
durationInFrames: Math.floor(durationInSeconds * 30),
fps: 30,
};
};
<Composition id="Audiogram" component={Audiogram} schema={mySchema}
width={1080} height={1080} calculateMetadata={calc} defaultProps={{src: ''}} />
The function receives {defaultProps, props, abortSignal, compositionId, isRendering} and may return its result synchronously or as a Promise. It can also override default render settings: defaultCodec, defaultOutName, defaultVideoImageFormat, defaultPixelFormat, defaultProResProfile, and defaultSampleRate.
<Still>
For single-image outputs (thumbnails, social cards, OG images), use <Still>. It's like <Composition> but has no fps or durationInFrames — a still is one frame.
import {Still} from 'remotion';
<Still id="Thumbnail" component={Thumbnail} width={1200} height={630}
defaultProps={{title: 'Launch day'}} />
<Folder>
Groups compositions in the Studio sidebar for organization. It has no effect on rendering.
import {Folder} from 'remotion';
<Folder name="Marketing">
<Composition id="Promo" {/* ... */} />
<Composition id="Teaser" {/* ... */} />
</Folder>
Type-safe, editable props with Zod
Pass a Zod schema and Remotion renders an editable form for defaultProps in the Studio, validates input props, and infers your component's prop types.
import {z} from 'zod';
const promoSchema = z.object({
title: z.string(),
accentColor: z.string(),
});
export const RemotionRoot = () => (
<Composition
id="Promo"
component={Promo}
schema={promoSchema}
defaultProps={{title: 'Hello', accentColor: '#0b84f3'}}
durationInFrames={150} fps={30} width={1920} height={1080}
/>
);
6. Animating
Animation in Remotion is "given the current frame, compute a value." Two functions do most of the work: interpolate and spring.
interpolate()
Maps an input range to an output range — the workhorse for fades, moves, and scales.
import {interpolate, useCurrentFrame} from 'remotion';
const frame = useCurrentFrame();
const opacity = interpolate(frame, [0, 30], [0, 1]);
Signature:
interpolate(input, inputRange, outputRange, options?)
| Option | Type | Default | Meaning |
|---|---|---|---|
easing |
easing fn or array of fns | linear | Curve applied between keyframes. As an array, one entry per segment (inputRange.length - 1). |
extrapolateLeft |
'extend' | 'clamp' | 'identity' | 'wrap' |
'extend' |
Behavior for inputs below the range. |
extrapolateRight |
same | 'extend' |
Behavior for inputs above the range. |
posterize |
number |
— | Snap the input to steps of this size (choppy/stop-motion look). |
By default values extend past the range. To hold the start/end values, clamp:
const opacity = interpolate(frame, [0, 30], [0, 1], {
extrapolateLeft: 'clamp',
extrapolateRight: 'clamp',
});
Warning
inputRangemust be strictly monotonically increasing.interpolate(frame, [0, 30, 30], ...)throwsinputRange must be strictly monotonically increasing.inputRangeandoutputRangemust also have the same length.
Multi-segment interpolation
Chain keyframes by passing more than two points (fade in, hold, fade out):
const opacity = interpolate(frame, [0, 20, 130, 150], [0, 1, 1, 0]);
String and tuple output ranges
interpolate can animate CSS strings (scale, translate, rotate) and numeric tuples directly:
// Animate a CSS transform value
const transform = interpolate(frame, [0, 30], ['translateY(0px)', 'translateY(100px)']);
// ⚠ Strings must be the same kind/units across the range.
// Animate a tuple (e.g. an [r, g, b] color)
const rgb = interpolate(frame, [0, 30], [[255, 0, 0], [0, 0, 255]]);
Easing
The Easing module provides curves to pass to interpolate's easing option.
import {interpolate, Easing} from 'remotion';
const x = interpolate(frame, [0, 30], [0, 200], {
easing: Easing.bezier(0.25, 0.1, 0.25, 1),
extrapolateRight: 'clamp',
});
| Easing | Description |
|---|---|
Easing.linear |
No easing. |
Easing.ease |
Default browser-style ease. |
Easing.quad, Easing.cubic |
Power curves. |
Easing.poly(n) |
Generic power curve of degree n. |
Easing.sin, Easing.circle, Easing.exp |
Trigonometric/exponential curves. |
Easing.elastic(bounciness) |
Springy overshoot. |
Easing.back(s) |
Slight backward anticipation. |
Easing.bounce |
Bouncing ball. |
Easing.bezier(x1,y1,x2,y2) |
Cubic Bézier (CSS-style). |
Easing.step0, Easing.step1 |
Step functions. |
Easing.in(fn), Easing.out(fn), Easing.inOut(fn) |
Modifiers that flip/mirror a curve. |
spring()
Physics-based animation that feels natural. Always pass frame and fps.
import {spring, useCurrentFrame, useVideoConfig} from 'remotion';
const frame = useCurrentFrame();
const {fps} = useVideoConfig();
const scale = spring({frame, fps, config: {damping: 200}});
| Parameter | Type | Default | Meaning |
|---|---|---|---|
frame |
number |
— | The current frame (required). |
fps |
number |
— | Frames per second (required). |
config.damping |
number |
10 |
Higher = less oscillation. Must be > 0. |
config.mass |
number |
1 |
Heavier = slower. |
config.stiffness |
number |
100 |
Higher = snappier. |
config.overshootClamping |
boolean |
false |
Prevent overshooting past to. |
from |
number |
0 |
Start value. |
to |
number |
1 |
End value. |
durationInFrames |
number |
— | Stretch/squash the spring to a fixed length. |
delay |
number |
0 |
Delay the animation by N frames. |
reverse |
boolean |
false |
Play the spring backwards. |
Tip
A spring returns ~0 → ~1 by default. Combine it with
interpolateto map that progress onto any range, or passfrom/todirectly.measureSpring()(also exported) tells you a spring's natural duration in frames.
interpolateColors()
Interpolates between colors (hex, rgb, rgba, hsl):
import {interpolateColors, useCurrentFrame} from 'remotion';
const color = interpolateColors(useCurrentFrame(), [0, 30], ['#ff0000', '#0000ff']);
random()
Math.random() is not allowed — it differs every call and breaks deterministic rendering. Use Remotion's seeded random() instead.
import {random} from 'remotion';
const jitter = random('particle-3') * 10; // same seed → same value, every render
Pass a string or number seed and you always get the same pseudo-random value in [0, 1). Passing null falls back to true randomness (rarely what you want).
Caution
Calling
random(null)(orMath.random()) inside a component makes renders non-reproducible and can cause flicker between frames during multi-threaded rendering. Always seed.
7. Sequencing & Timing
<Sequence>
Time-shifts its children: they see frame 0 when the sequence starts, and only appear during their window.
import {Sequence} from 'remotion';
<Sequence from={30} durationInFrames={60}>
<Title />
</Sequence>
| Prop | Type | Default | Description |
|---|---|---|---|
from |
number |
0 |
Frame at which children start (can be negative). |
durationInFrames |
number |
Infinity |
How long children are mounted. Must be positive. |
name |
string |
— | Label shown on the Studio timeline. |
layout |
'absolute-fill' | 'none' |
'absolute-fill' |
Whether to wrap children in an absolutely positioned div. |
premountFor |
number |
— | Mount (hidden) N frames early so assets/data are ready. |
postmountFor |
number |
— | Keep mounted (hidden) N frames after it ends. |
style |
CSS | — | Applied to the wrapper (only when layout="absolute-fill"). |
showInTimeline |
boolean |
true |
Whether to display this sequence on the timeline. |
Inside a sequence, useCurrentFrame() returns the local frame (0-based from from):
const Title = () => {
const frame = useCurrentFrame(); // 0 when the parent <Sequence from={30}> begins
// ...
};
Note
premountForis invaluable when a child does data fetching or media loading — it renders the child invisibly ahead of time so it's "warm" when it becomes visible.layout="none"is for when you don't want the wrapping div (e.g. wrapping a<tr>or text).
<Series>
Plays children one after another, auto-computing each one's from from the previous durations. No manual offset math.
import {Series} from 'remotion';
<Series>
<Series.Sequence durationInFrames={40}><Intro /></Series.Sequence>
<Series.Sequence durationInFrames={60}><Body /></Series.Sequence>
<Series.Sequence durationInFrames={40} offset={-10}><Outro /></Series.Sequence>
</Series>
<Series.Sequence> props: durationInFrames (required), offset (shift relative to the previous clip — negative overlaps), plus layout, name, and style.
<Loop>
Repeats its children.
import {Loop} from 'remotion';
<Loop durationInFrames={30} times={4}>
<Pulse />
</Loop>
| Prop | Type | Default | Description |
|---|---|---|---|
durationInFrames |
number |
— | Length of one iteration. |
times |
number |
Infinity |
How many times to loop (integer ≥ 0). |
layout |
'absolute-fill' | 'none' |
'absolute-fill' |
Wrapper behavior. |
Remotion automatically caps times so the loop never exceeds the composition's duration.
<Freeze>
Freezes its children at a specific frame.
import {Freeze} from 'remotion';
<Freeze frame={60}>
<Animation />
</Freeze>
It also accepts an active boolean to toggle freezing on and off.
8. Working with Media
Layout: <AbsoluteFill>
A div that fills its parent absolutely (top/left/right/bottom = 0, flex column). The standard way to stack full-screen layers.
import {AbsoluteFill} from 'remotion';
<AbsoluteFill style={{backgroundColor: 'black', justifyContent: 'center'}}>
<Title />
</AbsoluteFill>
Images: <Img>
Like <img>, but Remotion waits for it to load before capturing the frame (no half-loaded images in your render).
import {Img, staticFile} from 'remotion';
<Img src={staticFile('logo.png')} />
Static files: staticFile()
References a file in your project's public/ folder. Never build asset paths by hand.
staticFile('images/logo.png'); // → /images/logo.png (correctly encoded)
Caution
staticFile()rejects remote URLs, relative paths (./,..), absolute filesystem paths, and a leadingpublic/. For remote assets, pass the URL directly tosrcwithout wrapping it. Since Remotion 4,staticFile()performs URL encoding for you — don't pre-encode.
Audio: <Audio>
Plays audio in preview and includes it in the render.
import {Audio, staticFile} from 'remotion';
<Audio src={staticFile('music.mp3')} volume={0.5} />
Key props: volume (number or a function (frame) => number for fades), trimBefore / trimAfter (trim the clip in frames), playbackRate, muted, toneFrequency, and loopVolumeCurveBehavior.
// Fade the music in over the first 30 frames
<Audio src={staticFile('music.mp3')}
volume={(f) => interpolate(f, [0, 30], [0, 1], {extrapolateRight: 'clamp'})} />
Video: <OffthreadVideo> and <Video>
For embedding video, <OffthreadVideo> is the recommended component for rendering — it extracts exact frames off the main thread for frame-accurate, reliable output. <Video> (HTML5) is also available.
import {OffthreadVideo, staticFile} from 'remotion';
<OffthreadVideo src={staticFile('clip.mp4')} />
| Prop | Type | Description |
|---|---|---|
src |
string |
Required. Local (via staticFile) or remote URL. |
trimBefore |
number |
Trim N frames from the start. (replaces the deprecated startFrom) |
trimAfter |
number |
Trim to end at frame N. (replaces the deprecated endAt) |
volume |
number | function | Volume, optionally animated per frame. |
playbackRate |
number |
Speed multiplier. |
muted |
boolean |
Mute audio. |
transparent |
boolean |
Render with alpha (e.g. WebM/ProRes pipelines). |
toneMapped |
boolean |
Apply tone mapping for HDR sources. |
toneFrequency |
number |
Pitch correction factor. |
playbackRate |
number |
Speed up / slow down. |
audioStreamIndex |
number |
Choose which audio track to use. |
onVideoFrame |
function | Callback per frame (e.g. to draw to a canvas). |
delayRenderTimeoutInMilliseconds / delayRenderRetries |
number |
Tune the per-frame fetch timeout/retries. |
Important
startFrom/endAtwere renamed totrimBefore/trimAfter. The old names still work but are deprecated — prefer the new ones in new code.
Animated images & GIFs
<AnimatedImage>(core) plays animated AVIF/WebP/APNG.<Gif>from@remotion/gifplays GIFs frame-accurately (see the ecosystem section).
<IFrame>
Like <iframe>, but participates in Remotion's load-waiting so the render doesn't capture a blank frame.
9. Loading Data & Asynchronous Work
Rendering is frame-by-frame and synchronous by nature, so Remotion needs to know when your async work (data fetch, font load, image decode) is done. That's what delayRender / continueRender do.
delayRender() and continueRender()
Call delayRender() to tell Remotion "don't capture this frame yet," do your async work, then call continueRender(handle).
import {delayRender, continueRender, useCurrentFrame} from 'remotion';
import {useEffect, useState} from 'react';
const Data = () => {
const [data, setData] = useState(null);
const [handle] = useState(() => delayRender('Fetching data'));
useEffect(() => {
fetch('https://api.example.com/data')
.then((res) => res.json())
.then((json) => {
setData(json);
continueRender(handle);
});
}, [handle]);
return data ? <Chart data={data} /> : null;
};
delayRender option |
Type | Default | Meaning |
|---|---|---|---|
timeoutInMilliseconds |
number |
30000 |
How long to wait before failing the frame. |
retries |
number |
0 |
Retry the frame on timeout. |
Warning
If a
delayRender()handle is never cleared, the render fails with: "A delayRender() … was called but not cleared after 30000ms." EverydelayRender()must be matched by acontinueRender()(orcancelRender()). Pass a descriptive label so timeouts point you to the culprit.
cancelRender()
Abort the entire render with a clear error (e.g. when a fetch fails irrecoverably):
import {cancelRender} from 'remotion';
fetch(url).catch((err) => cancelRender(err));
prefetch()
Preloads a remote asset (audio/video/image) and returns {free, waitUntilDone}. Useful in the Player to fetch media before playback so it doesn't stutter.
Fonts
Use @remotion/google-fonts for Google Fonts or @remotion/fonts' loadFont() for local fonts — both integrate with the load-waiting system so text is ready before capture (see the ecosystem section).
10. The Remotion Studio
The Studio is a local web app for previewing and configuring your videos. Start it with:
npx remotion studio
# or specify an entry point:
npx remotion studio src/index.ts
Server ready - Local: http://localhost:3000
What you get:
- Composition list — every
<Composition>/<Still>, organized by<Folder>. - Live preview — hot-reloads on save.
- Timeline — scrub frames; sequences, audio, and video show as tracks.
- Props editor — if a composition has a Zod
schema, editdefaultPropswith a visual form and validation. - Render button — render directly from the UI, with a queue.
- Visual editing helpers — rulers, guides, keyboard shortcuts.
Tip
The
previewcommand is an alias forstudio. Common flags include--portto set the port and--propsto pass input props.
Studio behavior is configured in remotion.config.ts via Config.* (covered in the Admin Guide), for example Config.setStudioPort(3001) or Config.setKeyboardShortcutsEnabled(false).
11. Rendering Videos
Render a video
npx remotion render <entry-point?> <composition-id?> <output?>
Examples:
npx remotion render # render the only/default composition
npx remotion render MyComp # render a specific composition
npx remotion render MyComp out/video.mp4 # custom output path
+ Bundled 100%
+ Rendered 150/150
+ out/video.mp4 (2.1 MB)
Render a still image
npx remotion still MyStill out/thumb.png --frame=30
List compositions
npx remotion compositions
The following compositions are available:
Promo
Teaser
Thumbnail
Common render flags
These map directly to the configuration options; pass them on the CLI or set them in remotion.config.ts.
| Flag | Purpose |
|---|---|
--props='{"k":"v"}' or --props=props.json |
Input props for the composition. |
--codec=h264|h265|vp8|vp9|prores|gif |
Output codec. |
--image-format=jpeg|png |
Frame image format (png for transparency). |
--jpeg-quality=80 |
JPEG frame quality (0–100). |
--crf=<n> |
Quality/Constant Rate Factor (codec-dependent). |
--scale=2 |
Scale output dimensions by a factor. |
--frames=0-30 or --frames=30 |
Render a frame range or single frame. |
--concurrency=<n> |
Parallel rendering tabs (default: half your CPU threads). |
--muted |
Drop the audio track. |
--pixel-format=yuva420p |
Pixel format (e.g. for transparency). |
--every-nth-frame=2 |
For GIFs: pick every Nth frame. |
--log=verbose |
Verbose logging (browser logs, IO). |
--gl=angle|swangle|egl|... |
OpenGL backend for Chrome. |
--scale, --width/--height overrides |
Dimension overrides. |
Note
Transparency: render with
--codec=vp8/vp9(WebM) orproresand--image-format=png/--pixel-format=yuva420p. See the Admin Guide's encoding section for the full matrix.
Programmatic rendering (Node SSR)
For build pipelines and servers, render in code with @remotion/bundler + @remotion/renderer:
import {bundle} from '@remotion/bundler';
import {selectComposition, renderMedia} from '@remotion/renderer';
const serveUrl = await bundle({entryPoint: './src/index.ts'});
const composition = await selectComposition({
serveUrl,
id: 'MyComp',
inputProps: {title: 'Hello'},
});
await renderMedia({
serveUrl,
composition,
codec: 'h264',
outputLocation: 'out/video.mp4',
inputProps: {title: 'Hello'},
});
Key functions: bundle(), getCompositions() / selectComposition(), renderMedia(), renderStill(), renderFrames(), stitchFramesToVideo(), ensureBrowser(), and makeCancelSignal(). Full option references are in the Admin Guide.
12. Embedding Videos with the Player
@remotion/player lets you embed an interactive Remotion video inside any React app — no rendering, it plays your composition live in the browser.
npm i @remotion/player
import {Player} from '@remotion/player';
import {MyComp} from './MyComp';
export const App = () => (
<Player
component={MyComp}
durationInFrames={150}
compositionWidth={1920}
compositionHeight={1080}
fps={30}
controls
style={{width: '100%'}}
inputProps={{title: 'Hello'}}
/>
);
Selected <Player> props:
| Prop | Type | Description |
|---|---|---|
component / lazyComponent |
component | The composition to play. |
durationInFrames, fps, compositionWidth, compositionHeight |
number |
Video metadata (required). |
inputProps |
object | Props for your component. |
controls |
boolean |
Show the built-in control bar. |
autoPlay |
boolean |
Start playing on mount. |
loop |
boolean |
Loop playback. |
clickToPlay, doubleClickToFullscreen, spaceKeyToPlayOrPause |
boolean |
Interaction toggles. |
allowFullscreen |
boolean |
Enable fullscreen. |
showVolumeControls |
boolean |
Show volume UI. |
playbackRate |
number |
Speed. |
initialFrame |
number |
Frame to start at. |
inFrame / outFrame |
number | null |
Limit playback to a sub-range. |
renderLoading, renderPoster |
functions | Custom loading/poster UI. |
showPosterWhenPaused / ...WhenEnded / ...WhenBuffering |
boolean |
When to show the poster. |
renderPlayPauseButton, renderFullscreenButton, renderMuteButton, renderVolumeSlider, renderCustomControls |
functions | Fully custom controls. |
numberOfSharedAudioTags |
number |
Audio tag pool for autoplay reliability. |
schema |
Zod | Validate inputProps. |
acknowledgeRemotionLicense |
boolean |
Acknowledge the license (suppresses the console notice). |
Control the Player imperatively with a ref and the useCurrentPlayerFrame / usePlayer helpers, and listen to events (play, pause, seeked, ended, etc.) via addEventListener on the ref.
<Thumbnail> is a sibling component that renders a single frame as a poster image.
Tip
The Player's
componenttakes your composition component directly — not a<Composition>. Do not mount<Composition>inside a Player; Remotion throws an explicit error if you do.
13. The Package Ecosystem
Remotion ships dozens of @remotion/* packages. Install only what you use; each is versioned in lockstep with remotion. Here are the ones you'll reach for most.
Animation & transitions
@remotion/transitions — animate between scenes with <TransitionSeries>.
import {TransitionSeries, linearTiming, springTiming} from '@remotion/transitions';
import {fade} from '@remotion/transitions/fade';
import {slide} from '@remotion/transitions/slide';
<TransitionSeries>
<TransitionSeries.Sequence durationInFrames={60}><A /></TransitionSeries.Sequence>
<TransitionSeries.Transition presentation={fade()} timing={linearTiming({durationInFrames: 30})} />
<TransitionSeries.Sequence durationInFrames={60}><B /></TransitionSeries.Sequence>
</TransitionSeries>
Built-in presentations include: fade, slide, wipe, flip, clockWipe, iris, none, crossZoom, dreamyZoom, filmBurn, linearBlur, zoomBlur, book-flip, crosswarp, dissolve, ripple, swap, zoom-in-out. Timing functions: linearTiming, springTiming. Hook: useTransitionProgress.
@remotion/animation-utils — helpers like makeTransform and style interpolation for building CSS transform strings cleanly.
Graphics & drawing
| Package | What it gives you |
|---|---|
@remotion/shapes |
Components <Rect>, <Circle>, <Ellipse>, <Triangle>, <Star>, <Polygon>, <Pie>, <Heart>, <Arrow> + make* functions returning raw path data. |
@remotion/paths |
SVG path math: getLength, getPointAtLength, getTangentAtLength, evolvePath, interpolatePath, warpPath, translatePath, scalePath, reversePath, getBoundingBox, getSubpaths, and more. |
@remotion/noise |
noise2D, noise3D, noise4D simplex noise for organic motion. |
@remotion/svg-3d-engine |
Project and extrude SVG paths in 3D. |
@remotion/effects |
Visual effects like rings and zigzag. |
@remotion/motion-blur |
<CameraMotionBlur> and <Trail> for motion-blur/echo effects. |
@remotion/rounded-text-box, @remotion/starburst, @remotion/light-leaks, @remotion/animated-emoji |
Specialized visual building blocks. |
Rendering engines for other tech
| Package | Use |
|---|---|
@remotion/three |
<ThreeCanvas> — render react-three-fiber / Three.js scenes. |
@remotion/skia |
<SkiaCanvas> — React Native Skia drawing. |
@remotion/lottie |
<Lottie> + getLottieMetadata — play Lottie animations frame-accurately. |
@remotion/gif |
<Gif>, getGifDurationInSeconds, preloadGif — frame-accurate GIFs. |
@remotion/rive |
Render Rive animations. |
Audio, captions & media analysis
| Package | Use |
|---|---|
@remotion/media-utils |
getAudioData, useAudioData, visualizeAudio (waveform/spectrum for audiograms), getVideoMetadata, getImageDimensions, createSmoothSvgPath. |
@remotion/captions |
Caption type, parseSrt, serializeSrt for subtitles. |
@remotion/install-whisper-cpp, @remotion/openai-whisper, @remotion/elevenlabs |
Generate captions / speech via Whisper and ElevenLabs. |
@remotion/media-parser |
parseMedia / downloadAndParseMedia — pure-JS media metadata + frame parsing. |
@remotion/webcodecs |
convertMedia, encoders/decoders — in-browser media conversion via WebCodecs. |
@remotion/media |
New experimental_Audio / experimental_Video components (next-gen media pipeline). |
Fonts & assets
| Package | Use |
|---|---|
@remotion/google-fonts |
getAvailableFonts and per-font loadFont() — load Google Fonts with load-waiting built in. |
@remotion/fonts |
loadFont() for local font files. |
@remotion/preload |
preloadAudio, preloadVideo, preloadImage, preloadFont, resolveRedirect — warm up remote assets for the Player. |
@remotion/layout-utils |
measureText, fitText, fillTextBox — size text precisely (auto-fit headlines). |
Tooling
@remotion/tailwind / @remotion/tailwind-v4 (Tailwind support), @remotion/enable-scss (SCSS), @remotion/eslint-plugin and @remotion/eslint-config (linting), @remotion/zod-types (extra Zod types like color pickers in the Studio), @remotion/mcp and @remotion/skills (AI/agent integration).
14. Cloud & Server Rendering
For scale, render in the cloud. These are summarized here for users; full setup is in the Admin Guide.
AWS Lambda — @remotion/lambda
Render thousands of videos in parallel, each across many Lambda invocations.
import {renderMediaOnLambda, getRenderProgress} from '@remotion/lambda/client';
const {renderId, bucketName} = await renderMediaOnLambda({
region: 'us-east-1',
functionName: 'remotion-render-...',
serveUrl: 'https://...your-deployed-site...',
composition: 'MyComp',
codec: 'h264',
inputProps: {title: 'Hello'},
});
const progress = await getRenderProgress({renderId, bucketName, functionName, region});
You first deploy a Lambda function (deployFunction) and a bundled site (deploySite), then render against them. Output lands in S3. The same flags you use locally (codec, crf, scale, frame range, etc.) are accepted as input fields.
Google Cloud Run — @remotion/cloudrun
The GCP equivalent: deploy a Cloud Run service and a site, then render. Note: the Cloud Run integration is offered as an alpha by the Remotion team.
Self-hosted / containers
You can also run the Node SSR APIs (Section 11) on your own server or in Docker. The Admin Guide covers Dockerfiles and the required system libraries.
15. Common Workflows
A typical title animation
import {AbsoluteFill, interpolate, spring, useCurrentFrame, useVideoConfig} from 'remotion';
export const Title: React.FC<{text: string}> = ({text}) => {
const frame = useCurrentFrame();
const {fps} = useVideoConfig();
const enter = spring({frame, fps, config: {damping: 200}});
const opacity = interpolate(frame, [0, 20], [0, 1], {extrapolateRight: 'clamp'});
return (
<AbsoluteFill style={{justifyContent: 'center', alignItems: 'center'}}>
<h1 style={{opacity, transform: `scale(${enter})`}}>{text}</h1>
</AbsoluteFill>
);
};
An audiogram (waveform synced to audio)
- Load the audio with
useAudioDatafrom@remotion/media-utils. - Use
visualizeAudioto get amplitude bands for the current frame. - Render bars/shapes from those bands.
- Add
<Audio src={...} />so the sound is in the output. - Use
calculateMetadatato setdurationInFramesfrom the audio length.
The audiogram starter template wires all of this up — scaffold it with npx create-video@latest.
Captioned vertical video (TikTok style)
- Generate captions with
@remotion/install-whisper-cpp(or import an.srtandparseSrt). - Render words/segments timed with
<Sequence>based on caption timestamps. - Use
<OffthreadVideo>for the background clip.
The tiktok template demonstrates the full pipeline.
Data-driven videos at scale
- Build one parameterized composition with a Zod
schema. - Render once per dataset by passing different
--props(CLI) orinputProps(Lambda/SSR). - Use
calculateMetadatato adapt duration/size per dataset.
16. Troubleshooting
Error: A delayRender() "<label>" was called but not cleared after 30000ms
Cause: An async task started with delayRender() never called continueRender() (the promise rejected, a condition was never met, or the work genuinely takes longer than the timeout).
Solution: Ensure every delayRender() is matched by a continueRender(handle) on every code path, including errors. For genuinely slow work, raise the timeout: delayRender('label', {timeoutInMilliseconds: 60000}) or Config.setDelayRenderTimeoutInMilliseconds(60000).
Prevention: Always pass a descriptive label; the timeout message includes it and the call stack.
Error: inputRange must be strictly monotonically increasing but got [...]
Cause: An interpolate() inputRange has equal or decreasing values.
Solution: Make every value strictly larger than the last (e.g. [0, 30, 60], never [0, 30, 30]).
Prevention: When generating keyframes dynamically, sort and de-duplicate them.
Error: inputRange (n) and outputRange (m) must have the same length
Cause: Mismatched array lengths passed to interpolate().
Solution: Provide one output value per input keyframe.
Error: staticFile() does not support remote URLs / relative paths / absolute paths
Cause: Passing a URL, .//.. path, OS path, or a public/-prefixed path to staticFile().
Solution: For remote assets, pass the URL straight to src. For local files, pass the path inside public/ without the public/ prefix, e.g. staticFile('img/logo.png').
Error: Spring damping must be greater than 0
Cause: spring({config: {damping: 0}}).
Solution: Use a positive damping value (default is 10).
<Composition> mounted inside another composition / inside the component passed to <Player>
Cause: A <Composition> was placed inside another composition's component tree or passed as the Player's component.
Solution: <Composition> belongs only in the Root component. The Player takes your composition component directly, not <Composition>.
Animations work in preview but not in the render
Cause: Using wall-clock animation (CSS transitions/animations, requestAnimationFrame, setInterval, Date.now()) or unseeded Math.random().
Solution: Drive every animated value from useCurrentFrame() and use seeded random().
Video frames look wrong, duplicated, or stuttery in the render
Cause: Using <Video> (HTML5) for rendering, or a non-monotonic source.
Solution: Use <OffthreadVideo> for rendering; tune delayRenderTimeoutInMilliseconds/delayRenderRetries for slow remote sources.
Render is very slow
Cause: Low concurrency, heavy per-frame work, or large uncached assets.
Solution: Increase --concurrency, use premountFor on data-heavy sequences, prefetch remote media, and consider GPU/hardware acceleration (Admin Guide).
"Multiple versions of Remotion" warning
Cause: remotion / @remotion/* packages on different versions.
Solution: Align all of them to the exact same version (--save-exact).
17. FAQ
Q: Do I need to know video editing or FFmpeg? No. You write React. FFmpeg is bundled and used internally; you rarely touch it.
Q: Do I have to install FFmpeg or Chrome?
No. @remotion/renderer bundles FFmpeg and downloads its own Chrome Headless Shell automatically.
Q: What video formats can I export? H.264 (default), H.265, VP8, VP9 (WebM), ProRes, GIF, image sequences (PNG/JPEG), and audio-only outputs.
Q: How do I make a transparent video?
Render with a codec/pixel format that supports alpha (WebM VP8/VP9 or ProRes) and PNG frames / yuva420p. See the Admin Guide's encoding section.
Q: How do I change the video's duration based on data?
Use calculateMetadata on the <Composition> to compute durationInFrames at render time.
Q: How do I pass data into a video?
defaultProps on the composition, plus --props on the CLI or inputProps in SSR/Lambda. Validate with a Zod schema.
Q: Can I show a Remotion video in my web app without rendering?
Yes — use @remotion/player.
Q: Why is my random animation flickering between frames?
You're using Math.random() / random(null). Use a seeded random('some-seed') so each element is stable across frames.
Q: Can I use Tailwind / SCSS / TypeScript?
Yes — @remotion/tailwind(/-v4), @remotion/enable-scss, and TypeScript work out of the box.
Q: How do I render lots of videos cheaply and fast?
Render on AWS Lambda (@remotion/lambda) or Google Cloud Run (@remotion/cloudrun), parameterizing each render with different input props.
Q: Is Remotion free? Free for individuals, small for-profit companies (up to 3 employees), and non-profits. Larger for-profit organizations need a company license — see below.
18. Licensing
Remotion's source is publicly available, but it is not a standard open-source MIT project — it uses a two-tier license (SEE LICENSE IN LICENSE.md).
| Tier | Who | Cost |
|---|---|---|
| Free License | Individuals; for-profit organizations with up to 3 employees; non-profit / not-for-profit organizations; anyone evaluating Remotion before commercial use. | Free, including commercial video creation. |
| Company License | For-profit organizations above the free-tier size. | Paid (via remotion.pro). |
Allowed under the free license: using Remotion (commercially or non-commercially) to create videos and images, and modifying the source for your own use or to contribute fixes back.
Not allowed: copying or modifying Remotion to sell, rent, license, or sublicense your own derivative of Remotion.
Important
If your company needs a paid license, obtain a key from remotion.pro and set it with
Config.setPublicLicenseKey('...')(or'free-license'if you qualify for the free tier). The@remotion/licensingpackage and the Player'sacknowledgeRemotionLicenseprop relate to this system. This guide summarizes the terms; the authoritative text is in the project'sLICENSE.md, and the license changes slightly in Remotion 5.0.
19. Command & Reference Tables
CLI commands
Run any command with npx remotion <command>. Add --help for full flags.
| Command | Purpose |
|---|---|
studio (alias preview) |
Start the Remotion Studio. |
render |
Render a composition to a video/audio/image-sequence. |
still |
Render a single still image. |
compositions |
List compositions in a project. |
bundle |
Create a deployable bundle of your project. |
lambda |
AWS Lambda subcommands (deploy functions/sites, render, etc.). |
cloudrun |
Google Cloud Run subcommands. |
benchmark |
Benchmark render performance across concurrency settings. |
browser |
Manage the Chrome Headless Shell (e.g. ensure/download). |
gpu |
Report GPU / OpenGL backend info. |
ffmpeg / ffprobe |
Run the bundled FFmpeg / FFprobe. |
upgrade |
Upgrade all Remotion packages together. |
versions |
Show installed Remotion versions and check alignment. |
add |
Add a Remotion package to your project. |
skills |
AI agent skills integration. |
help |
Show CLI help. |
Core remotion exports (most-used)
| Category | Exports |
|---|---|
| Registration | registerRoot, Composition, Still, Folder |
| Hooks | useCurrentFrame, useVideoConfig, useBufferState, useCurrentScale |
| Animation | interpolate, interpolateColors, spring, measureSpring, Easing, random |
| Layout | AbsoluteFill |
| Timing | Sequence, Series, Loop, Freeze |
| Media | Audio, Video, OffthreadVideo, Img, IFrame, AnimatedImage, staticFile |
| Async | delayRender, continueRender, cancelRender, prefetch |
| Data | getInputProps, getStaticFiles, watchStaticFile |
Environment variables you may use
Variables prefixed REMOTION_ are exposed to your bundle. Common ones include REMOTION_AWS_ACCESS_KEY_ID / REMOTION_AWS_SECRET_ACCESS_KEY (Lambda credentials), REMOTION_GCP_* (Cloud Run), and a custom .env location via Config.setDotEnvLocation(). The full operational variable list is in the Admin Guide.