Remotion video
Skill Gekkos-tech/agency-os/plugins/motion-3d/skills/remotion-video
Best practices for building programmatic videos in React with Remotion v4+. Use when the user mentions "Remotion", "video in React", "programmatic video", "render a video from code", "generate an MP4 from React components", or wants data-driven, templated, or automated video production. Covers the frame-based mental model, compositions, deterministic rendering, interpolate/spring animation, Sequence/Series timing, assets and audio, performance, and CLI rendering. Not for browser animation of live websites — for animating DOM in a running web page (scroll effects, hover states, page transitions), use a GSAP skill instead; Remotion renders video files.From its SKILL.md
npx -y skills add Gekkos-tech/agency-os --skill remotion-videoAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
One thing to look at
- 4 stars4 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
SKILL.md
9.4 KB, ~2.2k tokens by cl100k_base, as published. Nobody here has run it
Remotion Video
Remotion turns React components into rendered video files. Every rule below exists because rendering happens frame by frame in headless browser instances: anything non-deterministic or time-based (instead of frame-based) produces flicker, desync, or broken renders.
Core mental model
- A video is a pure function of the current frame:
frame -> pixels. The same frame number must always produce the same image, on any machine, at any time, in any order. - Read the current frame with
useCurrentFrame(). Read fps, duration, width, height withuseVideoConfig(). - Never animate with wall-clock time,
setTimeout,requestAnimationFrame, CSS transitions, or CSS animations. During rendering there is no continuous time — frames are captured independently and possibly in parallel. All motion derives fromframe.
const frame = useCurrentFrame();
const { fps, durationInFrames, width, height } = useVideoConfig();
const opacity = interpolate(frame, [0, 30], [0, 1], {
extrapolateLeft: "clamp",
extrapolateRight: "clamp",
});
Compositions
Register videos in the root file with <Composition>:
<Composition
id="Promo"
component={Promo}
durationInFrames={30 * 10} // 10 s at 30 fps
fps={30}
width={1920}
height={1080}
defaultProps={{ title: "Hello" }}
/>
idmust be unique and is what you pass to the render CLI.- Express durations in terms of fps (
fps * seconds), never magic frame counts. - Use
<Still>for single-frame outputs (thumbnails, OG images). - For data-driven videos, use
calculateMetadataon the composition to fetch data once and derivedurationInFrames/ props from it — do not fetch inside components per frame.
<Composition
id="Podcast"
component={Podcast}
calculateMetadata={async ({ props }) => {
const data = await fetchEpisode(props.episodeId);
return {
props: { ...props, data },
durationInFrames: Math.ceil(data.durationSeconds * 30),
};
}}
...
/>
Determinism rules
- No
Math.random(). Userandom(seed)fromremotion— deterministic per seed. Give each element a stable seed (random(particle-${i})). - No
Date.now(),new Date(),performance.now()influencing visuals. - No CSS
transitionoranimationproperties. Set styles directly from interpolated values each frame. - No
useEffect-driven animation state (counters, tick loops). Derive everything fromuseCurrentFrame(). - Data fetching inside a component must be wrapped in
delayRender()/continueRender():
const [handle] = useState(() => delayRender("fetch data"));
useEffect(() => {
fetchData()
.then((d) => { setData(d); continueRender(handle); })
.catch((err) => cancelRender(err));
}, []);
Prefer calculateMetadata over in-component fetching: it runs once, not once per render instance, and avoids flicker from divergent responses across the parallel browser tabs used for rendering.
interpolate() and spring()
- Always clamp unless overshoot is intentional:
{ extrapolateLeft: "clamp", extrapolateRight: "clamp" }. Unclamped interpolations are the most common source of elements flying off-screen before/after their animation window. - Input ranges must be monotonically increasing. Multi-stop ranges express keyframes:
interpolate(frame, [0, 20, 80, 100], [0, 1, 1, 0])= fade in, hold, fade out. - Add easing via
Easingfromremotion:{ easing: Easing.out(Easing.cubic) }. spring()produces natural motion from 0 to 1; passfpsfromuseVideoConfig():
const scale = spring({ frame, fps, config: { damping: 200 } });
damping: 200= smooth, no overshoot (good default for UI). Lower damping = bouncier. UsedurationInFrameson spring (v4) to fit it into a fixed slot.- Combine: drive
interpolatewith a spring's output to map 0-1 onto any range.
See references/animation-patterns.md for ready-made recipes (fade, slide, scale-in, typewriter, counter, staggered lists).
Sequencing
<Sequence from={N} durationInFrames={M}>shifts its children's local frame to 0 at frame N and unmounts them outside the window. Inside a Sequence,useCurrentFrame()returns the local frame — write child animations starting at 0.<Series>with<Series.Sequence durationInFrames={...}>chains scenes back-to-back without manual offset arithmetic. Negativeoffsetoverlaps scenes.- Stagger repeated elements by rendering each in its own Sequence or by offsetting the frame per index:
{items.map((item, i) => (
<Sequence key={item.id} from={i * 5}>
<Item item={item} />
</Sequence>
))}
- Use
premountFor={frames}on Sequence when children load assets, so they mount (invisible) before appearing and are ready on their first visible frame.
Assets
- Put files in
public/and reference them withstaticFile("name.png"). Never use relative import paths or absolute filesystem paths for media. - Use Remotion's media components, never bare HTML tags:
<Img>,<OffthreadVideo>,<Audio>,<Video>. They ensure the asset is fully loaded before the frame is captured (bare<img>/<video>cause flicker and black frames). - Prefer
<OffthreadVideo>over<Video>for renders — it extracts frames via FFmpeg and is more reliable and seek-accurate. - Fonts: use
@remotion/google-fonts(loadFont()from e.g.@remotion/google-fonts/Inter) for Google fonts, or@remotion/fontsloadFont()withstaticFile()for local font files. Do not rely on CSS@importof font CDNs. - Preload remote assets with
@remotion/preload(preloadVideo,preloadAudio) for smoother Player playback.
Details in references/rendering-and-assets.md.
Audio
<Audio src={staticFile("music.mp3")} />; place inside a<Sequence>to delay its start.- Trim with
trimBefore/trimAfter(frames of the source to cut). - Volume ramps: pass a function of frame —
volume={(f) => interpolate(f, [0, 30], [0, 1], { extrapolateLeft: "clamp", extrapolateRight: "clamp" })}. The function receives the frame relative to audio start. Use this for fade-in/fade-out and ducking under voiceover. playbackRatechanges speed;looprepeats the clip.
Performance
- Every frame is a fresh React render. Keep per-frame work cheap: memoize heavy computation with
useMemokeyed on inputs that actually change, not onframe. - Avoid layout thrash: animate
transformandopacityrather thanwidth/top/leftwhere possible. - Move expensive one-time work (parsing data, building lookup tables) outside components or into
calculateMetadata. - Do not mount huge trees that are invisible — Sequences unmount children outside their window; use that.
- JSON/data props passed through compositions should be small; fetch big payloads in
calculateMetadata.
Rendering
- Preview:
npx remotion studio. - Render:
npx remotion render <composition-id> out/video.mp4. Props via--props='{"title":"Hi"}'or a JSON file path. - Stills:
npx remotion still <id> out/frame.png --frame=120. --concurrencycontrols parallel browser tabs (default ~half the CPU cores); raise for CPU-bound speedups, lower if memory-constrained.- Common flags:
--codec=h264(default MP4),--crffor quality,--frames=0-89to render a range. - Server-side/programmatic rendering:
@remotion/renderer(bundle,selectComposition,renderMedia); cloud-scale via@remotion/lambda.
Common pitfalls
- Flicker or elements "jumping": non-deterministic data (unseeded random, fetch races across render workers). Fix with
random(seed),calculateMetadata, ordelayRender. - Black/blank video frames: bare
<video>tag or missingstaticFile— use<OffthreadVideo>. - Animation ignores easing/looks linear in output but fine in Player: CSS transitions were doing the work in the browser; replace with frame interpolation.
- Element visible before its animation starts: missing extrapolation clamp.
- Spring feels wrong at different fps:
fpsnot passed fromuseVideoConfig().
Companion packages — when to reach for them
@remotion/transitions: scene-to-scene transitions (<TransitionSeries>with fade/slide/wipe presentations) instead of hand-rolling crossfades.@remotion/shapes: parametric SVG shapes (<Circle>,<Star>,<Pie>,<Triangle>) for motion-graphic primitives.@remotion/lottie: play After Effects/Lottie JSON synced to the Remotion timeline.@remotion/gif,@remotion/noise,@remotion/paths: GIF playback synced to frames, deterministic noise fields, SVG path interpolation/measurement.
Scope boundary
Remotion is for producing video files (or embeddable <Player> previews of them). If the goal is animating a live website — scroll-triggered effects, hover micro-interactions, page transitions — that is browser animation: use GSAP (see the gsap-* skills), not Remotion.
What ships with it: 2 files
12.5 KB alongside SKILL.md
references/
- animation-patterns.md5.9 KB
- rendering-and-assets.md6.6 KB