Remotion 4 Quick Reference
Build videos programmatically with React and TypeScript. Remotion treats video as a function of frames -- each frame is a React component render.
Current Version
| Package | Version | Notes |
|---|---|---|
| remotion | ~4.0.x | Remotion 4 is current stable. v5 migration guide exists but is not yet released. |
| @remotion/cli | ~4.0.x | CLI for studio, rendering, and Lambda |
| @remotion/renderer | ~4.0.x | Programmatic server-side rendering |
| @remotion/transitions | ~4.0.x | TransitionSeries and presentations (v4.0.53+) |
| @remotion/media-utils | ~4.0.x | Audio data, visualization, metadata |
| @remotion/media | ~4.0.x | WebCodecs-based Video component |
| @remotion/lambda | ~4.0.x | AWS Lambda serverless rendering |
Project Setup
# Create a new project
npx create-video@latest my-video
cd my-video
npm start # Opens Remotion Studio
Project Structure
my-video/
public/ # Static assets (images, audio, video files)
music.mp3
logo.png
src/
index.ts # Entry file -- registers Root component
Root.tsx # Composition registry
MyComposition.tsx # Video component(s)
remotion.config.ts # CLI configuration (studio, render)
package.json
tsconfig.json
Entry File (src/index.ts)
import {registerRoot} from 'remotion';
import {Root} from './Root';
registerRoot(Root);
Root File (src/Root.tsx)
import {Composition} from 'remotion';
import {MyVideo} from './MyVideo';
export const Root: React.FC = () => {
return (
<>
<Composition
id="MyVideo"
component={MyVideo}
durationInFrames={150}
fps={30}
width={1920}
height={1080}
defaultProps={{title: 'Hello World'}}
/>
</>
);
};
Every composition has four required properties: width, height, durationInFrames, and fps. The first frame is 0, the last frame is durationInFrames - 1.
Core Hooks
useCurrentFrame()
Returns the current frame number. This is the primary driver for all animation.
import {useCurrentFrame} from 'remotion';
export const MyComp: React.FC = () => {
const frame = useCurrentFrame();
return <div>Frame {frame}</div>;
};
useVideoConfig()
Returns composition metadata: fps, durationInFrames, width, height.
import {useVideoConfig} from 'remotion';
export const MyComp: React.FC = () => {
const {fps, durationInFrames, width, height} = useVideoConfig();
return <div>{width}x{height} at {fps}fps</div>;
};
Animation
interpolate()
Maps an input range to an output range. The workhorse for most animations.
import {interpolate, useCurrentFrame} from 'remotion';
export const FadeIn: React.FC = () => {
const frame = useCurrentFrame();
const opacity = interpolate(frame, [0, 30], [0, 1], {
extrapolateRight: 'clamp',
});
return <div style={{opacity}}>Hello</div>;
};
Parameters: interpolate(input, inputRange, outputRange, options?)
| Option | Values | Default | Purpose |
|---|---|---|---|
extrapolateLeft |
'extend' 'clamp' 'wrap' 'identity' |
'extend' |
Behavior below input range |
extrapolateRight |
'extend' 'clamp' 'wrap' 'identity' |
'extend' |
Behavior above input range |
easing |
(t: number) => number |
linear | Easing function |
Multi-point interpolation for complex animations:
// Fade in, hold, fade out
const opacity = interpolate(
frame,
[0, 20, durationInFrames - 20, durationInFrames],
[0, 1, 1, 0],
{extrapolateLeft: 'clamp', extrapolateRight: 'clamp'}
);
spring()
Physics-based animation. Animates from 0 to 1 by default with natural overshoot.
import {spring, useCurrentFrame, useVideoConfig} from 'remotion';
export const ScaleIn: React.FC = () => {
const frame = useCurrentFrame();
const {fps} = useVideoConfig();
const scale = spring({
frame,
fps,
config: {damping: 200},
});
return <div style={{transform: `scale(${scale})`}}>Pop!</div>;
};
Parameters:
| Param | Default | Purpose |
|---|---|---|
frame |
required | Current frame (from useCurrentFrame()) |
fps |
required | Frame rate (from useVideoConfig()) |
from |
0 |
Start value |
to |
1 |
End value |
config.mass |
1 |
Higher = slower |
config.stiffness |
100 |
Higher = bouncier |
config.damping |
10 |
Higher = less bounce. Use 200 for no overshoot. |
config.overshootClamping |
false |
Clamp at target value |
durationInFrames |
undefined | Stretch to exact duration |
delay |
0 |
Frames to wait before starting |
reverse |
false |
Reverse the animation |
measureSpring()
Calculate how many frames a spring animation takes to settle:
import {measureSpring} from 'remotion';
const duration = measureSpring({
fps: 30,
config: {damping: 200},
}); // e.g., 23 frames
Easing
Built-in easing functions for use with interpolate():
import {Easing, interpolate} from 'remotion';
const value = interpolate(frame, [0, 60], [0, 1], {
easing: Easing.bezier(0.25, 0.1, 0.25, 1),
extrapolateLeft: 'clamp',
extrapolateRight: 'clamp',
});
Available easings: linear, ease, quad, cubic, poly(n), sin, circle, exp, elastic, back, bounce, bezier(x1, y1, x2, y2).
Modifiers: Easing.in(fn), Easing.out(fn), Easing.inOut(fn).
Visualize at easings.net.
Timing and Sequencing
Sequence
Time-shifts children. Children's useCurrentFrame() resets to 0 at the sequence start.
import {Sequence, AbsoluteFill} from 'remotion';
export const MyVideo: React.FC = () => {
return (
<AbsoluteFill>
<Sequence from={0} durationInFrames={60}>
<Title />
</Sequence>
<Sequence from={60} durationInFrames={90}>
<Content />
</Sequence>
<Sequence from={150}>
<Outro />
</Sequence>
</AbsoluteFill>
);
};
Key props:
| Prop | Default | Purpose |
|---|---|---|
from |
0 |
Frame to start showing |
durationInFrames |
Infinity |
How long to show |
layout |
'absolute-fill' |
'none' to skip wrapper div |
name |
undefined | Label in Studio timeline |
premountFor |
undefined | Premount N frames before from |
style |
undefined | CSS on wrapper (not with layout="none") |
Nesting sequences accumulates offsets: inner from={60} inside outer from={30} = starts at frame 90.
Series
Sequential playback without manual from calculations:
import {Series} from 'remotion';
export const MyVideo: React.FC = () => {
return (
<Series>
<Series.Sequence durationInFrames={60}>
<Intro />
</Series.Sequence>
<Series.Sequence durationInFrames={90}>
<Main />
</Series.Sequence>
<Series.Sequence durationInFrames={60}>
<Outro />
</Series.Sequence>
</Series>
);
};
offset prop shifts timing: positive delays, negative overlaps with previous sequence.
Loop
Repeat content:
import {Loop} from 'remotion';
<Loop durationInFrames={50} times={3}>
<PulsingDot />
</Loop>
Loop.useLoop() returns {durationInFrames, iteration} inside a loop (v4.0.142+).
AbsoluteFill
Full-screen absolutely positioned container for layering:
import {AbsoluteFill} from 'remotion';
<AbsoluteFill>
<AbsoluteFill><Background /></AbsoluteFill>
<AbsoluteFill><Foreground /></AbsoluteFill>
</AbsoluteFill>
Renders as a div with position: absolute; top: 0; left: 0; right: 0; bottom: 0; width: 100%; height: 100%; display: flex; flex-direction: column.
Transitions
The @remotion/transitions package (v4.0.53+) provides TransitionSeries for animated transitions between scenes.
npm install @remotion/transitions
TransitionSeries
import {linearTiming, TransitionSeries} from '@remotion/transitions';
import {fade} from '@remotion/transitions/fade';
import {slide} from '@remotion/transitions/slide';
import {wipe} from '@remotion/transitions/wipe';
export const MyVideo: React.FC = () => {
return (
<TransitionSeries>
<TransitionSeries.Sequence durationInFrames={60}>
<SceneA />
</TransitionSeries.Sequence>
<TransitionSeries.Transition
presentation={fade()}
timing={linearTiming({durationInFrames: 30})}
/>
<TransitionSeries.Sequence durationInFrames={90}>
<SceneB />
</TransitionSeries.Sequence>
<TransitionSeries.Transition
presentation={slide({direction: 'from-right'})}
timing={linearTiming({durationInFrames: 20})}
/>
<TransitionSeries.Sequence durationInFrames={60}>
<SceneC />
</TransitionSeries.Sequence>
</TransitionSeries>
);
};
Duration math: During a transition both scenes render simultaneously. Total duration = sum of sequences minus sum of transitions. E.g. 60 + 90 + 60 - 30 - 20 = 160 frames.
Presentations
| Presentation | Import | Options |
|---|---|---|
fade() |
@remotion/transitions/fade |
shouldFadeOutExitingScene (default false) |
slide() |
@remotion/transitions/slide |
direction: from-left, from-right, from-top, from-bottom |
wipe() |
@remotion/transitions/wipe |
direction: 8 directions including diagonals |
Timing Functions
| Function | Import | Notes |
|---|---|---|
linearTiming({durationInFrames}) |
@remotion/transitions |
Constant speed |
springTiming({config, durationInFrames}) |
@remotion/transitions |
Spring physics. Use durationRestThreshold: 0.001 for smooth cutoff. |
TransitionSeries.Overlay (v4.0.415+)
Renders content at a cut point without affecting timeline duration:
<TransitionSeries.Overlay durationInFrames={20} offset={-10}>
<FlashEffect />
</TransitionSeries.Overlay>
Media Components
Static Files
Place assets in public/ and reference with staticFile():
import {staticFile} from 'remotion';
const src = staticFile('my-image.png'); // resolves to /my-image.png
staticFile() auto-encodes URI-unsafe characters. Do not pre-encode filenames.
Img
Enhanced <img> that delays rendering until loaded:
import {Img, staticFile} from 'remotion';
<Img src={staticFile('photo.png')} style={{width: '100%'}} />
From v4.0.0, failed images trigger cancelRender() unless handled via onError. Max resolution: 539 megapixels.
OffthreadVideo (Recommended for Rendering)
Extracts frames via FFmpeg outside the browser. Frame-perfect accuracy, widest codec support.
import {OffthreadVideo, staticFile} from 'remotion';
<OffthreadVideo
src={staticFile('screen-recording.mp4')}
volume={0.8}
playbackRate={1.5}
style={{width: '100%'}}
/>
Key props: src, volume, playbackRate, muted, transparent, trimBefore, trimAfter, toneFrequency, toneMapped.
Not supported in client-side rendering. Cannot loop (wrap in <Loop> instead).
Html5Video
Browser-native <video>. Supports looping. Less frame-accurate than OffthreadVideo.
import {Html5Video, staticFile} from 'remotion';
<Html5Video src={staticFile('clip.mp4')} loop muted />
Video (from @remotion/media)
WebCodecs-based. Fastest rendering. Supports partial downloads and looping. Experimental. Falls back to OffthreadVideo for unsupported formats.
import {Video} from '@remotion/media';
<Video src={staticFile('clip.mp4')} />
Video Tag Comparison
| Component | Speed | Frame Accuracy | Looping | Client Rendering |
|---|---|---|---|---|
OffthreadVideo |
Fast | Frame-perfect | No (use Loop) | No |
Html5Video |
Medium | Not guaranteed | Yes | No |
Video (@remotion/media) |
Fastest | Good | Yes | Yes |
Recommendation: Use OffthreadVideo for production rendering. Use Video from @remotion/media when you need client-side rendering or maximum speed.
Audio
Html5Audio (previously Audio)
import {Html5Audio, staticFile} from 'remotion';
<Html5Audio src={staticFile('narration.mp3')} />
Volume control:
// Static
<Html5Audio src={staticFile('bg.mp3')} volume={0.3} />
// Dynamic fade-in over 30 frames
<Html5Audio
src={staticFile('voice.mp3')}
volume={(f) =>
interpolate(f, [0, 30], [0, 1], {extrapolateLeft: 'clamp'})
}
/>
Trimming (in frames):
<Html5Audio src={staticFile('audio.mp3')} trimBefore={60} trimAfter={120} />
Other props: playbackRate, muted, loop, toneFrequency.
Audio Visualization
import {useAudioData, visualizeAudio} from '@remotion/media-utils';
import {Html5Audio, staticFile, useCurrentFrame, useVideoConfig} from 'remotion';
export const AudioBars: React.FC = () => {
const frame = useCurrentFrame();
const {fps} = useVideoConfig();
const audioData = useAudioData(staticFile('music.mp3'));
if (!audioData) return null;
const visualization = visualizeAudio({
fps,
frame,
audioData,
numberOfSamples: 32, // must be power of 2
smoothing: true,
});
return (
<div style={{display: 'flex', alignItems: 'flex-end', height: 200}}>
{visualization.map((v, i) => (
<div
key={i}
style={{
width: 10,
height: 200 * v,
backgroundColor: 'white',
marginRight: 2,
}}
/>
))}
</div>
);
};
visualizeAudio() returns an array of 0-1 values. Lower indices = bass, higher = treble. Use optimizeFor: 'speed' for Lambda rendering.
Data-Driven Videos
Zod Schemas for Props
Define props with Zod for type safety and visual editing in Remotion Studio:
npx remotion add @remotion/zod-types zod
import {z} from 'zod';
import {Composition} from 'remotion';
const videoSchema = z.object({
title: z.string(),
subtitle: z.string(),
backgroundColor: z.string(),
});
type VideoProps = z.infer<typeof videoSchema>;
const MyVideo: React.FC<VideoProps> = ({title, subtitle, backgroundColor}) => {
return <div style={{backgroundColor}}><h1>{title}</h1><p>{subtitle}</p></div>;
};
// In Root.tsx
<Composition
id="MyVideo"
component={MyVideo}
schema={videoSchema}
defaultProps={{title: 'Hello', subtitle: 'World', backgroundColor: '#000'}}
durationInFrames={150}
fps={30}
width={1920}
height={1080}
/>
The @remotion/zod-types package adds special types: zColor(), zTextarea(), zMatrix().
calculateMetadata()
Dynamically compute composition metadata based on props or fetched data:
import {CalculateMetadataFunction, Composition} from 'remotion';
const calculateMetadata: CalculateMetadataFunction<MyProps> = async ({
props,
defaultProps,
abortSignal,
}) => {
const data = await fetch('https://api.example.com/video-data', {
signal: abortSignal,
}).then((r) => r.json());
return {
props: {...props, data},
durationInFrames: data.scenes.length * 150,
fps: 30,
width: 1920,
height: 1080,
};
};
// In Root.tsx
<Composition
id="DataVideo"
component={DataVideo}
calculateMetadata={calculateMetadata}
defaultProps={{data: null}}
durationInFrames={300}
fps={30}
width={1920}
height={1080}
/>
Runs once before rendering. Can set durationInFrames, width, height, fps, props, defaultCodec, and defaultOutName.
Async Data and Loading
delayRender / continueRender
Pause rendering until async data loads:
import {useCallback, useEffect, useState} from 'react';
import {continueRender, delayRender} from 'remotion';
export const AsyncComp: React.FC = () => {
const [handle] = useState(() => delayRender('Loading data...'));
const [data, setData] = useState(null);
useEffect(() => {
fetch('https://api.example.com/data')
.then((r) => r.json())
.then((d) => {
setData(d);
continueRender(handle);
})
.catch((err) => cancelRender(err));
}, [handle]);
if (!data) return null;
return <div>{data.title}</div>;
};
Default timeout: 30 seconds. Override with delayRender('label', {timeoutInMilliseconds: 10000}).
prefetch()
Pre-load media assets for the Player:
import {prefetch} from 'remotion';
const {free, waitUntilDone} = prefetch('https://example.com/video.mp4', {
method: 'blob-url',
});
await waitUntilDone();
// Asset is now cached. Call free() when done.
Deterministic Randomness
Never use Math.random() -- it produces different values across render threads.
import {random} from 'remotion';
const value = random('my-seed'); // always 0.073...
const x = random(`particle-x-${i}`); // deterministic per index
const truly = random(null); // true randomness (opt-in)
Rendering
CLI Rendering
# Render a video
npx remotion render MyVideo output.mp4
# Render with options
npx remotion render MyVideo output.mp4 \
--codec=h264 \
--crf=18 \
--concurrency=4
# Render a still frame
npx remotion still MyVideo frame.png --frame=30
# Render as image sequence
npx remotion render MyVideo --sequence
# Render audio only
npx remotion render MyVideo output.mp3 --codec=mp3
# Render a GIF
npx remotion render MyVideo output.gif
# Pass props
npx remotion render MyVideo output.mp4 --props='{"title":"Custom"}'
# Benchmark concurrency
npx remotion benchmark
Key CLI flags:
| Flag | Purpose |
|---|---|
--codec |
h264, h265, vp8, vp9, prores, mp3, aac, wav, h264-mkv |
--crf |
Quality (lower = better). H.264 default ~18 |
--concurrency |
Parallel frame rendering. Find optimal with benchmark |
--scale |
Output scale factor (e.g., 0.5 for half resolution) |
--jpeg-quality |
JPEG quality 0-100 |
--frames |
Render specific range (e.g., 0-59) |
--muted |
Skip audio |
--hardware-acceleration |
GPU acceleration (v4.0.228+) |
--log |
Verbosity: error, warn, info, verbose |
Programmatic Rendering (SSR)
import {bundle} from '@remotion/bundler';
import {renderMedia, selectComposition} from '@remotion/renderer';
const bundled = await bundle({entryPoint: './src/index.ts'});
const composition = await selectComposition({
serveUrl: bundled,
id: 'MyVideo',
inputProps: {},
});
await renderMedia({
composition,
serveUrl: bundled,
codec: 'h264',
outputLocation: 'output.mp4',
});
Lambda Rendering
Serverless rendering on AWS. Fastest and most scalable option.
# Setup
npx remotion lambda policies role # Create AWS role
npx remotion lambda functions deploy # Deploy Lambda function
npx remotion lambda sites create src/index.ts # Deploy site to S3
# Render
npx remotion lambda render <site-url> MyVideo
Programmatic:
import {renderMediaOnLambda, getRenderProgress} from '@remotion/lambda';
const {renderId, bucketName} = await renderMediaOnLambda({
region: 'us-east-1',
functionName: 'remotion-render',
composition: 'MyVideo',
serveUrl: siteUrl,
codec: 'h264',
inputProps: {},
});
Constraints: videos under ~80 min at 1080p, ~5GB max output, 1000 concurrent Lambdas per region (increasable).
Still Frames
npx remotion still MyVideo poster.png --frame=0
import {renderStill} from '@remotion/renderer';
await renderStill({
composition,
serveUrl: bundled,
output: 'poster.png',
frame: 0,
});
Remotion Studio
The development environment for previewing and editing compositions.
npx remotion studio
Features:
- Live preview of all compositions
- Visual props editor (requires Zod schema)
- Render button with progress tracking and queuing
- Timeline visualization of sequences
- Can be deployed for team access
Player Component
Embed Remotion videos in any React app:
npm install @remotion/player
import {Player} from '@remotion/player';
import {MyVideo} from './MyVideo';
export const App = () => {
return (
<Player
component={MyVideo}
durationInFrames={150}
fps={30}
compositionWidth={1920}
compositionHeight={1080}
style={{width: 800}}
controls
inputProps={{title: 'Hello'}}
/>
);
};
Player best practices:
- Memoize
inputPropswithuseMemo()to prevent unnecessary re-renders - Keep Player isolated from frequently updating state (e.g., current time)
- Pass browser events to
.play()and.toggle()to satisfy autoplay restrictions
Performance
Rendering Speed
- Use
npx remotion benchmarkto find optimal--concurrencyvalue - JPEG is faster than PNG (PNG required for transparency)
- VP8/VP9 encode slower due to compression
- Lower resolution with
--scalewhen quality isn't critical - Use
--log=verboseto identify slowest frames - Prefer
Videofrom@remotion/mediafor fastest rendering
Component Performance
- Use
useMemo()anduseCallback()to cache expensive computations - Memoize components with
React.memo()when props don't change every frame - Minimize external data fetching; cache in localStorage when possible
- Avoid GPU effects (blur, drop-shadow, WebGL) in cloud rendering -- pre-render to images instead
Bundle Optimization
- Keep bundles small for faster Lambda cold starts
- Avoid large dependencies in video components
- Use dynamic imports for heavy libraries
Common Patterns
Narration Sync
Place audio and visuals in the same timeline using Sequences:
export const NarratedVideo: React.FC = () => {
return (
<AbsoluteFill>
{/* Background music at low volume */}
<Html5Audio src={staticFile('bg-music.mp3')} volume={0.15} />
{/* Narration audio */}
<Sequence from={0}>
<Html5Audio src={staticFile('narration.mp3')} />
</Sequence>
{/* Visuals synced to narration timestamps */}
<Sequence from={0} durationInFrames={90}>
<TitleCard text="Welcome" />
</Sequence>
<Sequence from={90} durationInFrames={120}>
<ScreenRecording src={staticFile('demo.mp4')} />
</Sequence>
<Sequence from={210} durationInFrames={60}>
<CallToAction />
</Sequence>
</AbsoluteFill>
);
};
Screen Recording with Overlays
export const DemoOverlay: React.FC = () => {
const frame = useCurrentFrame();
const {fps} = useVideoConfig();
const cursorScale = spring({frame: frame - 30, fps, config: {damping: 200}});
return (
<AbsoluteFill>
<OffthreadVideo src={staticFile('screen-recording.mp4')} />
<AbsoluteFill>
{/* Animated callout */}
<Sequence from={30} durationInFrames={90}>
<div
style={{
position: 'absolute',
top: 200,
left: 400,
transform: `scale(${cursorScale})`,
background: 'rgba(255,0,0,0.3)',
borderRadius: 8,
padding: 20,
border: '2px solid red',
}}
>
<p style={{color: 'white', fontSize: 24}}>Click here!</p>
</div>
</Sequence>
</AbsoluteFill>
</AbsoluteFill>
);
};
Text Animation
export const AnimatedTitle: React.FC<{text: string}> = ({text}) => {
const frame = useCurrentFrame();
const {fps} = useVideoConfig();
return (
<AbsoluteFill style={{justifyContent: 'center', alignItems: 'center'}}>
<h1 style={{fontSize: 80, display: 'flex'}}>
{text.split('').map((char, i) => {
const delay = i * 3;
const y = interpolate(frame - delay, [0, 10], [40, 0], {
extrapolateLeft: 'clamp',
extrapolateRight: 'clamp',
});
const opacity = interpolate(frame - delay, [0, 10], [0, 1], {
extrapolateLeft: 'clamp',
extrapolateRight: 'clamp',
});
return (
<span key={i} style={{transform: `translateY(${y}px)`, opacity}}>
{char === ' ' ? '\u00A0' : char}
</span>
);
})}
</h1>
</AbsoluteFill>
);
};
Progress Bar
export const ProgressBar: React.FC = () => {
const frame = useCurrentFrame();
const {durationInFrames} = useVideoConfig();
const progress = interpolate(frame, [0, durationInFrames], [0, 100], {
extrapolateRight: 'clamp',
});
return (
<div
style={{
position: 'absolute',
bottom: 0,
left: 0,
height: 4,
width: `${progress}%`,
backgroundColor: '#3b82f6',
}}
/>
);
};
Fade-to-Black Transition
export const FadeToBlack: React.FC<{children: React.ReactNode}> = ({children}) => {
const frame = useCurrentFrame();
const {durationInFrames} = useVideoConfig();
const fadeOut = interpolate(
frame,
[durationInFrames - 15, durationInFrames],
[0, 1],
{extrapolateLeft: 'clamp', extrapolateRight: 'clamp'}
);
return (
<AbsoluteFill>
{children}
<AbsoluteFill style={{backgroundColor: `rgba(0,0,0,${fadeOut})`}} />
</AbsoluteFill>
);
};
TypeScript Configuration
Remotion projects use TypeScript by default. Key tsconfig settings:
{
"compilerOptions": {
"target": "ES2018",
"module": "commonjs",
"jsx": "react-jsx",
"strict": true,
"noEmit": true,
"esModuleInterop": true,
"lib": ["es2018", "dom"]
}
}
Props must use type (not interface) when passing to compositions.
Configuration File
remotion.config.ts at project root configures CLI behavior:
import {Config} from '@remotion/cli/config';
Config.setOverwriteOutput(true);
Config.setVideoImageFormat('jpeg');
Config.setConcurrency(4);
Flat API in v4 -- no more nested namespaces like Config.Bundling.x().
Key v4 Changes from v3
- FFmpeg bundled (no external install needed) via Rust binary
- Studio replaces Preview (visual props editor, render button)
Configimport moved to@remotion/cli/config, flat APIqualityrenamed tojpegQualityparallelismrenamed toconcurrency<Audio>renamed to<Html5Audio><Video>(from remotion) renamed to<Html5Video>- New
<Video>from@remotion/media(WebCodecs-based) - Props must be
type, notinterface defaultPropsrequired for components with propsimageFormatsplit intosetVideoImageFormat()/setStillImageFormat()- New packages:
@remotion/rive,@remotion/shapes,@remotion/tailwind - New
calculateMetadata()API for data-driven compositions selectComposition()API for picking compositions programmatically- Minimum Node 16.0.0