Remotion: Make Real Videos With React Components
Making video has always lived in a different universe from making web apps. One world is React, CSS, and Git; the other is After Effects timelines, Premiere projects, and hand-tuned FFmpeg filter graphs that nobody wants to touch twice. Remotion collapses those worlds together. It is a framework for creating real videos, actual MP4, WebM, and GIF files, using nothing but React components.
The mental model is delightfully simple: a video is a function of a frame number. You build your scene as a React tree of divs, SVGs, images, canvas, and WebGL, and Remotion renders frames 0 through durationInFrames - 1 by screenshotting each one with headless Chrome and stitching the results together with FFmpeg. Because it is a real browser, everything you already know from the web just works: Flexbox, CSS transitions, Tailwind, Three.js, Lottie, and more.
That makes Remotion a natural fit for data-driven video at scale: personalized "year in review" campaigns, per-user reports, automated social clips, subtitle rendering, and reusable video templates that live in version control and get reviewed in pull requests like any other code.
Why Reach For It
- The whole web platform is your toolbox: CSS, SVG, Canvas, WebGL via React Three Fiber, Lottie, and Tailwind all render natively.
- Parameterized video: pass props into a composition and render thousands of variants from data instead of editing each one by hand.
- Deterministic, parallelized rendering that can scale onto AWS Lambda for massively parallel jobs.
- TypeScript-first with strong types throughout, plus a rich ecosystem of packages for audio, captions, transitions, GIFs, and embedding an interactive player.
- Everything is code: your "video editor" becomes a Git repo of React components.
A Word On Licensing (Read This First)
Before you fall in love, understand what you are signing up for. Remotion is source-available, not open source. It does not use a standard MIT or Apache license.
It is free for individuals, non-profits, and for-profit companies with up to three employees. Once a for-profit company has more than three people and uses Remotion commercially, it must buy a Company License through remotion.pro, which uses seat-based pricing and also covers Lambda usage tiers.
This regularly trips up teams who see the GitHub repository and assume it is free for any commercial use. It is not. If you are a startup of five engineers shipping Remotion-rendered videos to customers, you need a paid license. Budget for it up front so nobody is surprised later.
Getting Set Up
The fastest path is the scaffolding command, but you can also add it to an existing project.
npm install remotion @remotion/cli
yarn add remotion @remotion/cli
Remotion needs Chrome Headless Shell and FFmpeg to render. It downloads and manages these for you, so there is usually nothing extra to install by hand. Once it is in place, launch the studio to preview your work:
npx remotion studio
This opens a browser-based editor where you can scrub the timeline, tweak props live, and hit "Render video" without touching the command line.
Declaring Your First Video
Everything starts with a composition. A <Composition> pairs a React component with the metadata Remotion needs to render it: dimensions, frame rate, and duration. Compositions are registered in 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" }}
/>
);
};
Here durationInFrames={150} at fps={30} gives you a five-second clip. The defaultProps are the data your component receives, and you can override them at render time to produce different variants from the same template.
Animating With The Frame
Every animation in Remotion is a function of the current frame. The useCurrentFrame() hook returns the frame being rendered, and useVideoConfig() gives you the composition metadata so your component stays resolution- and fps-independent.
The two workhorses are interpolate(), which maps a value from one range to another, and spring(), which produces physics-based motion.
import {
AbsoluteFill,
interpolate,
spring,
useCurrentFrame,
useVideoConfig,
} from "remotion";
export const MyVideo: React.FC<{ title: string }> = ({ title }) => {
const frame = useCurrentFrame();
const { fps } = useVideoConfig();
const opacity = interpolate(frame, [0, 20], [0, 1], {
extrapolateRight: "clamp",
});
const scale = spring({ frame, fps, config: { damping: 12 } });
return (
<AbsoluteFill
style={{
backgroundColor: "#0b1021",
justifyContent: "center",
alignItems: "center",
}}
>
<h1
style={{
color: "white",
opacity,
transform: `scale(${scale})`,
}}
>
{title}
</h1>
</AbsoluteFill>
);
};
AbsoluteFill is a convenience component: an absolutely positioned div that fills the entire frame, perfect for stacking full-screen layers. Notice the extrapolateRight: "clamp" option on interpolate. Without it, the function extrapolates past the input range by default, which means your opacity would happily climb above 1. Clamping is the single most common fix for "why does my fade look wrong" questions.
Arranging Scenes On A Timeline
Real videos have scenes that start and stop. The <Sequence> component time-shifts and time-limits its children. A child inside <Sequence from={30}> sees useCurrentFrame() return 0 when the parent timeline is at frame 30, because its own timeline is rebased. The durationInFrames prop unmounts the child after its window closes.
import { Sequence } from "remotion";
export const Timeline: React.FC = () => {
return (
<>
<Sequence durationInFrames={30}>
<Intro />
</Sequence>
<Sequence from={30} durationInFrames={60}>
<Main />
</Sequence>
<Sequence from={90}>
<Outro />
</Sequence>
</>
);
};
This is how you compose multi-scene videos without manually subtracting frame offsets in every component. Each scene animates as if it started at frame zero, and Remotion handles placing it on the global timeline.
Bringing In Assets And Audio
Remotion ships components designed for deterministic rendering. Use <Img> instead of a raw <img> so a frame never renders before the image has loaded, leaving you with a blank flash. Reference files from the public/ folder with staticFile() rather than relative imports.
import { AbsoluteFill, Img, Audio, staticFile } from "remotion";
export const WithAssets: React.FC = () => {
return (
<AbsoluteFill>
<Img src={staticFile("background.png")} />
<Audio src={staticFile("music.mp3")} volume={0.6} />
</AbsoluteFill>
);
};
For embedding existing video, reach for <OffthreadVideo>. It extracts frames outside the browser, which makes rendering faster and more reliable, and it is the recommended default over the older <Video> component. The <Audio> component accepts volume (which can itself be a function of the frame for fades), startFrom, and endAt props.
Rendering For Real
Previewing in the studio is fun, but eventually you need a file. There are several routes, scaling from a single command to a fleet of cloud workers.
The CLI handles one-off renders and accepts a --props JSON string to parameterize the output:
npx remotion render MyVideo out/video.mp4 --props='{"title":"Shipped!"}'
For server-side rendering inside your own backend, the @remotion/renderer package gives you full programmatic control. You bundle the project, select the composition, and render media to a file.
import { bundle } from "@remotion/bundler";
import { renderMedia, selectComposition } from "@remotion/renderer";
import path from "node:path";
const serveUrl = await bundle({
entryPoint: path.resolve("src/index.ts"),
});
const composition = await selectComposition({
serveUrl,
id: "MyVideo",
inputProps: { title: "Generated" },
});
await renderMedia({
composition,
serveUrl,
codec: "h264",
outputLocation: "out/video.mp4",
inputProps: { title: "Generated" },
});
selectComposition() resolves metadata, including dynamically calculated durations, and renderMedia() does the heavy lifting. There is also renderStill() when you only need a single image, such as an Open Graph thumbnail.
When you need to render thousands of personalized videos quickly, @remotion/lambda deploys a render function to AWS Lambda. Each Lambda renders a chunk of frames in parallel and the chunks are concatenated, with renderMediaOnLambda() as the entry point. A Google Cloud Run option (@remotion/cloudrun) and GitHub Actions templates exist too.
Staying Deterministic
Because Remotion screenshots the same frame and expects an identical result every time, rendering must be deterministic. A few habits keep you out of trouble.
Avoid Math.random() and Date.now(); they produce different output on every render. Remotion provides a seeded random() function instead. When a frame depends on async data, fonts, or assets, use delayRender() and continueRender() to tell Remotion to wait before screenshotting.
import { delayRender, continueRender } from "remotion";
import { useEffect, useState } from "react";
export const WithData: React.FC = () => {
const [handle] = useState(() => delayRender());
const [data, setData] = useState<string | null>(null);
useEffect(() => {
fetch("https://api.example.com/stats")
.then((res) => res.json())
.then((json) => {
setData(json.headline);
continueRender(handle);
});
}, [handle]);
return <AbsoluteFill>{data ?? "Loading"}</AbsoluteFill>;
};
One more thing to internalize: there is no wall-clock time during rendering. CSS animation and transition, along with requestAnimationFrame-based libraries, do not advance while Remotion renders. Drive everything from useCurrentFrame() instead.
Embedding A Player In Your App
Rendering is only half the story. The @remotion/player package lets you drop an interactive <Player> into a normal React app for runtime playback, no rendering required. The same composition you render server-side can be previewed live in the browser, which is perfect for letting users customize a video before you render the final file.
import { Player } from "@remotion/player";
import { MyVideo } from "./MyVideo";
export const Preview: React.FC = () => {
return (
<Player
component={MyVideo}
durationInFrames={150}
fps={30}
compositionWidth={1920}
compositionHeight={1080}
inputProps={{ title: "Live preview" }}
controls
/>
);
};
Conclusion
Remotion is a genuinely clever reframing of what video creation can be. By treating a frame as a pure function of a number and leaning on the entire web platform for rendering, it lets React developers produce broadcast-quality, data-driven video without ever opening a traditional editor. The studio, the CLI, the programmatic renderer, and the Lambda pipeline cover everything from a quick one-off to industrial-scale personalization.
Just remember the one thing that catches teams off guard: this is source-available software, not open source. Hobbyists, non-profits, and tiny companies render for free, but any for-profit company with more than three employees needs a paid Company License. Factor that into your decision, and Remotion becomes one of the most powerful and pleasant ways to make video with code that exists today.