Back

Making Videos with Claude Code and Remotion

Making Videos with Claude Code and Remotion

Claude Code with the Remotion skill is a workflow for generating video from natural-language prompts: install the skill, scaffold a Remotion project, describe the video in plain English, and let Claude write frame-accurate React that renders to MP4. That’s the whole loop. The friction is never the prompt — it’s understanding the code Claude hands back well enough to fix it when the rendered output drifts from what you asked for. This article walks one developer-facing example end to end, then shows you how to read the Remotion mental model so you can diagnose a broken render from the Studio preview instead of re-prompting blind.

Key Takeaways

  • Remotion is a React framework that renders video by treating each frame as a pure function of a frame number, accessed via useCurrentFrame(), and screenshots that output to produce an MP4.
  • At 30fps, a 15-second video is 450 frames; every animation is a mapping from frame number to a CSS value, with no timeline editor involved.
  • The install command has appeared in several forms across tutorials — verify the current one against remotion.dev/docs/ai/claude-code before running, because the skills tooling has been in flux.
  • When animations fire simultaneously in the preview, the cause is almost always missing from offsets on <Sequence> components, which default to starting at frame 0.
  • Remotion renders by launching headless Chromium and screenshotting every frame, which is why a 30-second composition at 30fps requires 900 screenshots and why Remotion Lambda exists for production pipelines.

What this stack is

Remotion is an open-source React framework for creating videos programmatically: you define scenes as React components, control timing with frame numbers, and render the output as MP4, WebM, or GIF — no timeline editor, no drag-and-drop. The Remotion skill for Claude Code is a rule pack that teaches the agent Remotion’s API surface — compositions, sequences, interpolate(), spring(), render configuration — so it generates correct frame math instead of guessing. It is not a plugin that runs at render time; it is context injected into Claude so the code it writes compiles and animates the way you described.

What is the Remotion mental model?

In Remotion, your video is a pure function of a frame number. A composition is a React component with a fixed duration in frames. At 30fps, a 15-second video is 450 frames, and every animation is expressed as a mapping from the current frame to a CSS value — opacity, transform, color. There is no timeline; there is only math.

Four primitives carry the whole model, all documented in the Remotion fundamentals:

  • useCurrentFrame() returns the frame being rendered right now. Your component re-runs for every frame.
  • fps and duration live on the <Composition>. Per the composition docs, you set durationInFrames, fps, width, and height there.
  • <Sequence> shifts time. A child wrapped in <Sequence from={90}> sees frame 0 when the global frame is 90, which is how you stagger scenes.
  • interpolate() maps a frame range to a value range.

Frame math you’ll reach for constantly:

DurationFrames at 30fps
5s150
10s300
15s450
30s900
60s1800

Once you internalize “frame in, CSS value out,” the generated code stops looking like magic.

Prerequisites and install

You need Node.js (check the version requirement on the Remotion getting-started page before installing) and Claude Code, Anthropic’s terminal coding agent. Claude Code runs against a Claude subscription or API billing — confirm the current access requirements in Anthropic’s Claude Code docs, since plan availability changes.

Now the part every tutorial rushes past. The install command for the Remotion skill has appeared in at least three different shapes across recent write-ups (npx @anthropic-ai/skills add remotion, npx skills add remotion, npx skills add remotion-dev/skills). The skills packaging has been moving, so don’t trust a copied command. Open the canonical skills page — remotion.dev/docs/ai/skills — and run whatever it currently lists. Treat that page as the single source of truth until the ecosystem settles.

Verify Claude Code is on your PATH first:

claude --version

Then install the skill using the command from the official docs page, and confirm it landed by asking Claude inside a project: “Do you have the Remotion skill loaded?”

A worked example: a 15-second feature-reveal clip

The strongest developer use cases for this stack are assets you regenerate from data or on a schedule: a changelog clip built from CHANGELOG.md, an animated architecture diagram that reflects the current system, or a product-feature reveal re-rendered every release. We’ll build the feature reveal.

Step 1 — Scaffold

The scaffold command is documented on the Remotion getting-started page:

npx create-video@latest feature-reveal
cd feature-reveal
npm install

Create the project with a TypeScript template or configuration. The scaffold gives you src/Root.tsx (where compositions register) and a starter composition.

Step 2 — Prompt Claude Code

Open the agent in the project and be explicit about frame math — this is where most bad output originates:

Create a Remotion composition called FeatureReveal.
- 15 seconds at 30fps (450 frames), 1920x1080.
- Dark background (#0d1117).
- A headline "Ship changelogs as video" fades in over frames 0-30,
  holds, then fades out over frames 420-450.
- Three feature rows below the headline, each sliding up from 40px
  with a spring, staggered: row 1 enters at frame 60, row 2 at 90,
  row 3 at 120.
- Register FeatureReveal in Root.tsx with the correct
  durationInFrames and fps.

Step 3 — Read the generated code

A correct generation looks close to this. The headline uses interpolate(); the rows use <Sequence from> offsets plus spring():

import {
  AbsoluteFill,
  interpolate,
  spring,
  Sequence,
  useCurrentFrame,
  useVideoConfig,
} from "remotion";

const features = ["One prompt", "Re-render per release", "Version-controlled"];

const FeatureRow: React.FC<{ label: string }> = ({ label }) => {
  const frame = useCurrentFrame();
  const { fps } = useVideoConfig();

  // spring() returns 0→1; we map it to translateY and opacity.
  const enter = spring({ frame, fps, config: { damping: 14 } });
  const translateY = interpolate(enter, [0, 1], [40, 0]);

  return (
    <div style={{ opacity: enter, transform: `translateY(${translateY}px)` }}>
      {label}
    </div>
  );
};

export const FeatureReveal: React.FC = () => {
  const frame = useCurrentFrame();

  // Headline opacity: in by frame 30, hold, out from 420 to 450.
  const headlineOpacity = interpolate(
    frame,
    [0, 30, 420, 450],
    [0, 1, 1, 0],
    { extrapolateRight: "clamp" }
  );

  return (
    <AbsoluteFill style={{ backgroundColor: "#0d1117", color: "white" }}>
      <h1 style={{ opacity: headlineOpacity, fontSize: 72 }}>
        Ship changelogs as video
      </h1>
      {features.map((label, i) => (
        // Each row starts 30 frames after the last via `from`.
        <Sequence key={label} from={60 + i * 30}>
          <FeatureRow label={label} />
        </Sequence>
      ))}
    </AbsoluteFill>
  );
};

The line worth memorizing is the headline interpolate(). The call maps frame numbers to opacity: at frame 0 opacity is 0, by frame 30 it’s 1, it holds through frame 420, then fades to 0 by frame 450. The frame array and value array must be the same length, and { extrapolateRight: "clamp" } stops the value from continuing past the last point — the option name and behavior are documented in the interpolate() reference.

The stagger comes from from={60 + i * 30} on each <Sequence>. Because each row is its own sequence, its useCurrentFrame() resets to 0 at its start frame, so spring() fires from the row’s entry point rather than the video’s start.

Claude must also register the composition in Root.tsx:

import { Composition } from "remotion";
import { FeatureReveal } from "./FeatureReveal";

export const RemotionRoot: React.FC = () => (
  <Composition
    id="FeatureReveal"
    component={FeatureReveal}
    durationInFrames={450}
    fps={30}
    width={1920}
    height={1080}
  />
);

If durationInFrames and fps here disagree with the frame numbers in your component, the timing breaks. This is the single most common source of “the video is the wrong length.”

Step 4 — Preview in Studio

Start the Remotion Studio:

npm run dev

The Studio opens in your browser (note the port it prints — it isn’t always the same). Select FeatureReveal, press play, and scrub the playhead. Scrubbing to a specific frame is the core debugging move: the preview shows you exactly what frame N renders, so you can compare it against the frame math in your code.

How do you diagnose a Remotion render that looks wrong?

Diagnose from the Studio preview, not from a list of generic mistakes. Scrub to the frame where the problem appears and read the code that controls that frame range. Three failure modes account for most of them.

Everything animates at once. When animations fire simultaneously in the preview, the cause is almost always missing from offsets on <Sequence> components: each scene defaults to starting at frame 0 unless you set from={startFrame}. Scrub to frame 0 — if all three rows are already moving, the sequences aren’t staggered. Fix it by asking: “Wrap each feature row in its own <Sequence> with from set to 60, 90, and 120.”

The video is the wrong length. Claude generates frame numbers from the fps you state. If you don’t specify fps, it assumes a default and may miscalculate against a composition configured differently. Scrub to the end of the playhead: if your 450-frame animation finishes at frame 300, the composition’s durationInFrames is set to 300. Re-prompt with the exact numbers — “15 seconds at 30fps is 450 frames; set durationInFrames={450}” — rather than vague “make it longer.”

Motion feels robotic. Linear interpolate() with no easing reads as mechanical. Scrub through an entry animation: if it moves at constant speed, there’s no easing. Swap to spring() for organic motion (it returns a 0→1 value you map onto a transform), or pass an easing option to interpolate() per the interpolate docs. Ask: “Use spring() for the row entry instead of a linear interpolate.”

Session replays of embedded demo videos on landing pages frequently reveal a separate failure mode — autoplaying a large MP4 that blocks paint — but that’s a delivery concern, not a render one.

Render to MP4

Render from the command line with npx remotion render, passing the composition id and an output path:

npx remotion render FeatureReveal out/feature-reveal.mp4

Here is the reality the prompt dumps gloss over. Remotion renders by launching headless Chromium, screenshotting every frame, and piping the frames through ffmpeg — the architecture is described in the rendering docs. A 30-second composition at 30fps means 900 browser screenshots. That’s why render time scales with frame count and per-frame complexity, and why a long or visually heavy composition can take meaningful wall-clock time on a laptop rather than rendering “in under a minute.”

For production pipelines — long videos, many variants, CI that can’t tie up a build agent — Remotion’s documented cloud path is Remotion Lambda, which fans the frames out across parallel Lambda functions. Lambda is worth the setup cost once local render time becomes a bottleneck or you need to render on a schedule without a developer’s machine in the loop. For a one-off 15-second clip, render locally and skip it.

Remotion is free for individuals and small companies; larger companies need a license — check the current terms on the Remotion license page before shipping commercially.

When should you not use Remotion?

Reach for Remotion when the video is data-driven, repeatable, or has to match a design system exactly — a changelog clip, an animated diagram, a per-release feature reveal. Reach for a traditional editor or an AI video generator when the content is live-action, photorealistic, or a one-off creative piece where the iteration cost of re-rendering outweighs the benefit of code-based control. Cutting bloopers out of a talking-head recording, color-grading footage, or producing organic, photoreal motion are all jobs Remotion will fight you on. The dividing line is whether the output benefits from being a program: if you’ll render it once and never touch it again, code is overhead, not an advantage.

The payoff of this stack is the second render, not the first. Once a composition exists, regenerating it from new data — a new changelog, a new metric, a new release — is a single command. Install the skill from the official docs page, build the feature-reveal example above, and then point the same composition at data you actually ship.

FAQs

interpolate() maps a frame range to a value range linearly by default, so you control exact start and end frames, like fading opacity from 0 to 1 between frames 0 and 30. spring() returns a physics-based value from 0 to 1 that eases in and settles naturally, which you map onto a transform or opacity. Use interpolate() for precise timing and holds; use spring() for organic motion that should feel less mechanical.

The duration is controlled by durationInFrames on the Composition in Root.tsx, not by the animation values inside your component. If durationInFrames is 300 but your animation runs to frame 450, the render stops at frame 300 and cuts the rest. The fps value must also match the math you used: at 30fps, 15 seconds is 450 frames. Set both explicitly so the component math and the composition config agree.

You can describe videos in plain English and let Claude Code generate the React, but you still need to read the output to fix it when the render drifts from what you asked. The skill teaches Claude Remotion's API so it produces correct frame math, but it does not remove the need to understand compositions, sequences, and frame numbers when debugging. Knowing the mental model of frame in, CSS value out is what lets you diagnose problems from the Studio preview.

Remotion Lambda is worth the setup cost once local render time becomes a bottleneck or you need to render on a schedule without a developer's machine in the loop. Because Remotion screenshots every frame through headless Chromium, render time scales with frame count and per-frame complexity, so long videos or many variants tie up a laptop or build agent. Lambda fans frames out across parallel functions. For a one-off short clip, render locally and skip Lambda.

Gain Debugging Superpowers

Unleash the power of session replay to reproduce bugs, track slowdowns and uncover frustrations in your app. Get complete visibility into your frontend with OpenReplay — the most advanced open-source session replay tool for developers. Check our GitHub repo and join the thousands of developers in our community.

OpenReplay