Git Branching for Remotion Template Revisions
By RenderComp Team Editorial policy
Remotion renders video by running your React component tree once per frame. Given identical props and an identical frame number, the output is always the same set of pixels. That determinism is not a curiosity of the runtime. It makes a Remotion template a software artifact rather than a binary blob — Git becomes the right version-control layer for managing client revisions.
A change to a template is a change to source code. A git diff between two branches is a complete, reviewable record of what changed between revision 1 and revision 2. The remaining question is how to structure those branches so the diff stays useful rather than noisy. Any previously approved MP4 should be reproducible from a checkout weeks or months later.
Without a branching strategy, revisions accumulate as commented-out variables, files named MyComp_FINAL_v3.tsx, or props objects with six boolean flags. The decision tree those flags encode usually lives in one person’s memory. The workflow below prevents that from the first feedback round.
Branch topology
Three branch roles cover the common project lifecycle. main holds only approved commits. revision/ branches isolate each round of client changes. feat/ branches handle in-progress work on individual animations.
Every commit on main is associated with a Git tag recording which render was delivered:
git tag -a client-v1 -m "Approved render delivered 2026-09-16"
git push origin client-v1
Revision work happens on a branch like revision/v2. When the client approves, merge with --no-ff to preserve the branch topology in the log, then tag the merge commit:
git checkout main
git merge --no-ff revision/v2
git tag -a client-v2 -m "Revision v2 approved"
git push origin main --follow-tags
feat/ branches merge into the active revision branch, not into main. This keeps main free of speculative changes and keeps the tag-to-commit mapping unambiguous.
Making compositions diff-friendly
The diff between two revision branches is only useful if it shows exactly what changed. An inline literal value buried inside JSX requires the reviewer to understand the surrounding component logic just to evaluate its impact. Moving every configurable value into a Zod schema solves this. Remotion’s schema integration validates prop types in the Studio and ensures the diff lands exactly where reviewers expect it.
// src/schema.ts
import { z } from "zod";
export const MyCompSchema = z.object({
accentColor: z.string(),
headlineText: z.string().max(80),
logoOpacity: z.number().min(0).max(1).default(1),
fadeInFrames: z.number().int().min(1).default(20),
});
export type MyCompProps = z.infer<typeof MyCompSchema>;
Register the schema and its defaults in Root.tsx:
// src/Root.tsx
import { Composition } from "remotion";
import { MyComp } from "./MyComp";
import { MyCompSchema } from "./schema";
export const RemotionRoot: React.FC = () => (
<Composition
id="MyComp"
component={MyComp}
schema={MyCompSchema}
defaultProps={{
accentColor: "#1a73e8",
headlineText: "Your headline here",
logoOpacity: 1,
fadeInFrames: 20,
}}
durationInFrames={150}
fps={30}
width={1920}
height={1080}
/>
);
The component receives typed props and derives all animation values from them:
// src/MyComp.tsx
import {
useCurrentFrame,
useVideoConfig,
spring,
interpolate,
AbsoluteFill,
} from "remotion";
import type { MyCompProps } from "./schema";
export const MyComp: React.FC<MyCompProps> = ({
accentColor,
headlineText,
logoOpacity,
fadeInFrames,
}) => {
const frame = useCurrentFrame();
const { fps } = useVideoConfig();
const opacity = spring({
fps,
frame,
config: {
damping: 200, // critically damped, no overshoot
stiffness: 80,
},
durationInFrames: fadeInFrames,
});
const y = interpolate(frame, [0, fadeInFrames], [40, 0], {
extrapolateRight: "clamp",
});
return (
<AbsoluteFill style={{ backgroundColor: "#fff" }}>
<h1
style={{
color: accentColor,
opacity,
transform: `translateY(${y}px)`,
}}
>
{headlineText}
</h1>
</AbsoluteFill>
);
};
A git diff revision/v1..revision/v2 now shows changed defaultProps values and nothing else, unless the animation logic itself changed. A reviewer with no prior knowledge of the project can evaluate that diff in a short time.
The stiffness: 80 and damping: 200 combination is worth noting specifically because damping values above roughly 2 * sqrt(stiffness * mass) (where mass defaults to 1) put the spring into overdamped territory: it reaches its target without oscillating. For UI text fades, that behavior is almost always what you want. A reviewer reading a revision diff who sees stiffness change from 80 to 120 knows immediately that the animation got snappier; if they see damping drop from 200 to 20, they know it now bounces. The schema keeps those decisions visible and deliberate.
Pinning Remotion versions across branches
Reproducibility across branches depends on pinned package versions. Before v4.0.246, additional arguments passed to npx remotion upgrade were silently ignored. After v4.0.246, those arguments take effect as documented. This matters for multi-branch projects: if one branch pins an older release while a revision branch upgrades mid-cycle, the two branches may produce different renders for the same composition and the same props.
Pin every @remotion/* package explicitly in package.json:
{
"dependencies": {
"remotion": "4.0.246",
"@remotion/cli": "4.0.246",
"@remotion/renderer": "4.0.246"
}
}
Commit the lock file alongside every version change. A postinstall guard catches the common mistake of upgrading one package but not the others:
{
"scripts": {
"postinstall": "node scripts/check-remotion-versions.js"
}
}
// scripts/check-remotion-versions.js
const pkg = require("../package.json");
const entries = Object.entries({
...pkg.dependencies,
...(pkg.devDependencies ?? {}),
}).filter(([name]) => name === "remotion" || name.startsWith("@remotion/"));
const versions = new Set(entries.map(([, v]) => v));
if (versions.size > 1) {
console.error("Mismatched @remotion/* versions:", Object.fromEntries(entries));
process.exit(1);
}
This script exits non-zero if any @remotion/* package carries a different version string, failing npm install before the mismatch reaches a render job.
When a revision branch needs a Remotion upgrade, run npx remotion upgrade on that branch and commit the updated package.json and lock file together. The version bump then appears as a discrete commit in git log --oneline main..revision/v3, separate from prop and layout changes.
Reproducing an approved render
Tagging approved commits closes the reproducibility loop. Checking out a tag restores the component tree, the schema defaults, and the pinned Remotion version.
git checkout client-v1
npm ci
npx remotion render MyComp out/client-v1-rerender.mp4
npm ci reads only the lock file and exits with an error if package.json and the lock file are out of sync. That strictness is correct behavior when reproducing from a historical tag. Any drift in installed packages would produce a render that differs from the original in ways that are hard to trace back to a specific cause.
The tag message (-m "Approved render delivered 2026-09-16") stores the delivery date as a human-readable annotation. git tag -v client-v1 retrieves it later without needing to search commit messages or external records.
Finding starting components
RenderComp exposes its template library as an MCP server, so Claude, Cursor, or ChatGPT can search template components and retrieve actual source code rather than generating approximations. When you start a new revision branch, fetching a reference component through the MCP server gives you a prop-schema-driven starting point that fits the branching workflow above: schema.ts is already separate, defaultProps are already extracted, and the composition is diffable from the first commit.
Now available
Get 1,000+ Remotion Templates
Pay once — no subscription. Lifetime updates. TypeScript-first.
View pricing →Free 50
Get the 50 templates as a ZIP
Enter your email and the ZIP link arrives right away.
Send me the 50-template ZIP. I agree to receive RenderComp template updates and product news, including the paid library (a few emails, one-click unsubscribe). Privacy policy
You do not have to use email. The GitHub repository stays public and needs no signup. Open the repository
On its way
Check your inbox for the ZIP link, and your spam folder if it is not there. You can also take it straight from GitHub right now.