Remotion vs HyperFrames: Same Video, Real Render Times
Updated 2026-11-02
On our test, Remotion rendered the same video in about 16 seconds and HyperFrames in about 27. HyperFrames is free for any company and needs no project install; Remotion is free for up to three people and paid above that. Pick Remotion if you write React or care most about speed. Pick HyperFrames if you start from HTML or your company has four or more people.
Glidezo isn’t connected to Remotion or HeyGen, and neither has seen or approved this test. Our own free skills use HyperFrames, so we have a reason to favour it. It lost the speed test anyway, and we report that as measured. The clips above are built with Remotion and HyperFrames. Remotion’s licence is here.
The test
Both versions draw the same clip with the same timing, font and easing: 8 seconds, 1920x1080, 30 fps, 240 frames. Frames from the two look the same to the eye, and differ by about 0.4 to 0.5 out of 255 on average.
The computer: Linux, 4 CPU cores, 15 GB of memory and no GPU, with other small services running. Times are from pressing Enter to the command finishing, three runs each, on 2026-11-02. Chrome was already downloaded.
| Settings | Remotion 4.0.540 | HyperFrames 0.8.223 | HyperFrames 0.9.3 |
|---|---|---|---|
| Each tool’s defaults | 20.6 s, 16.1 s, 15.4 s (173 KB) | 28.9 s, 27.6 s, 28.7 s (345 KB) | 31.6 s, 28.7 s, 27.3 s (345 KB) |
| Same settings: 2 workers, CRF 20 | 15.3 s, 16.1 s, 16.3 s (148 KB) | 26.6 s, 28.6 s, 26.3 s (254 KB) | Not run |
What this does and doesn’t show:
- Remotion was about 11 seconds faster on this clip. HyperFrames’ own log said 12 to 15 seconds of rendering. In the run we traced, another 12 seconds passed before its render pipeline began. On longer videos that head start should matter less, but we only timed this length.
- The file sizes differ even at the same CRF. They come from different encoder settings, so don’t read them as a quality score.
- We also tried HyperFrames installed in the project, not through
npx: 26.3 s, 27.4 s and 27.2 s, sonpxwasn’t the cause. - It is one simple clip on one computer. Video layers, 3D or many effects could change the order. We didn’t test those.
Setup, as measured
| Step | Remotion | HyperFrames |
|---|---|---|
| Start a project | npm install: 22 s, 266 MB in node_modules for the bare minimum |
Nothing to install in the project: npx fetches the tool once (111 MB if you do install it) |
| Official starter | npx create-video --yes --blank my-video: 76 s, 340 MB |
npx hyperframes init my-video: not timed |
| Our clip’s code, when timed | 56 lines of TypeScript, in 3 files | 54 lines of HTML, in 1 file |
| Needs | Node.js | Node.js 22+ and FFmpeg |
The same animation in each
Remotion works out the picture from the frame number. HyperFrames builds a paused GSAP timeline in seconds, and the renderer moves it to each frame’s time.
// Remotion
const line = (start: number) => progress(frame, fps, start, 0.6);
<div style={{ opacity: line(0.2), transform: `translateY(${(1 - line(0.2)) * 60}px)` }}>Same video.</div>
// HyperFrames
tl.from("#same", { opacity: 0, y: 60, duration: 0.6, ease }, 0.2);
The HyperFrames line is shorter, but it comes with rules: mark up the timing, register the timeline, and don’t use the wall clock. Remotion’s way is more typing, but each frame is plain arithmetic, which many people find easier to reason about.
The two sources below are complete, and these are the extra files they need. Both load Geist from a file, so copy geist-latin-600-normal.woff2 from the @fontsource/geist package next to the HyperFrames page and into Remotion’s public/ folder. The HyperFrames page also needs gsap.min.js 3.15.0 beside it. Then:
# Remotion (npm install remotion@4.0.540 @remotion/cli@4.0.540 react@19.2.0 react-dom@19.2.0)
npx remotion render src/index.ts Comparison out.mp4 --concurrency=2 --crf=20
# HyperFrames, in the folder with index.html
npx --yes hyperframes@0.8.223 render . --workers 2 --crf 20 --output out.mp4
Full Remotion source: src/index.ts and src/Root.tsx
Root.tsx also registers a second clip, used on our Remotion guide. That one is FrameDemo.tsx, under it.
import { registerRoot } from "remotion";
import { Root } from "./Root";
registerRoot(Root);
import { Composition } from "remotion";
import { FrameDemo } from "./FrameDemo";
import { SameVideo } from "./SameVideo";
export const Root = () => (
<>
<Composition id="Comparison" component={SameVideo} durationInFrames={240} fps={30} width={1920} height={1080} />
<Composition id="FrameDemo" component={FrameDemo} durationInFrames={180} fps={30} width={1920} height={1080} />
</>
);
import { AbsoluteFill, Easing, interpolate, staticFile, useCurrentFrame } from "remotion";
const font = `@font-face { font-family: "Geist"; font-weight: 600; src: url("${staticFile("geist-latin-600-normal.woff2")}") format("woff2"); }`;
/** The picture is worked out from the frame number, and nothing else. */
export const FrameDemo = () => {
const frame = useCurrentFrame();
const left = interpolate(frame, [20, 110], [0, 1400], { extrapolateLeft: "clamp", extrapolateRight: "clamp", easing: Easing.inOut(Easing.cubic) });
const opacity = interpolate(frame, [0, 15, 165, 179], [0, 1, 1, 0]);
return (
<AbsoluteFill style={{ background: "#0a0a0b", color: "#f4f4f5", fontFamily: "Geist", fontWeight: 600, padding: "130px", opacity }}>
<style>{font}</style>
<div style={{ fontSize: 84 }}>A video is a function of its frame number.</div>
<div style={{ marginTop: 90, fontSize: 200, color: "#8b7bff" }}>frame {frame}</div>
<div style={{ position: "relative", marginTop: 90, height: 90 }}>
<div style={{ position: "absolute", left, width: 90, height: 90, borderRadius: 18, background: "#8b7bff" }} />
</div>
<div style={{ position: "absolute", left: 130, bottom: 110, fontSize: 38, color: "#a1a1aa" }}>Rendered with Remotion. Made by Glidezo, not by Remotion.</div>
</AbsoluteFill>
);
};
Full Remotion source: src/SameVideo.tsx
import { AbsoluteFill, Easing, interpolate, staticFile, useCurrentFrame, useVideoConfig } from "remotion";
const BARS = [
{ label: "Title", width: 1180, start: 1.5 },
{ label: "Bars", width: 860, start: 1.7 },
{ label: "Counter", width: 1420, start: 1.9 },
];
const font = `@font-face { font-family: "Geist"; font-weight: 600; src: url("${staticFile("geist-latin-600-normal.woff2")}") format("woff2"); }`;
/** Moves from 0 to 1 between two times in seconds, easing out like GSAP's power2.out. */
const progress = (frame: number, fps: number, start: number, length: number) =>
interpolate(frame / fps, [start, start + length], [0, 1], {
extrapolateLeft: "clamp",
extrapolateRight: "clamp",
easing: Easing.out(Easing.cubic),
});
export const SameVideo = () => {
const frame = useCurrentFrame();
const { fps } = useVideoConfig();
const line = (start: number) => progress(frame, fps, start, 0.6);
const outro = progress(frame, fps, 7.2, 0.6);
return (
<AbsoluteFill style={{ background: "#0a0a0b", color: "#f4f4f5", fontFamily: "Geist", fontWeight: 600 }}>
<style>{font}</style>
<div style={{ position: "absolute", inset: 0, padding: "110px 130px", opacity: 1 - outro }}>
<div style={{ fontSize: 124, lineHeight: 1.1, opacity: line(0.2), transform: `translateY(${(1 - line(0.2)) * 60}px)` }}>Same video.</div>
<div style={{ fontSize: 124, lineHeight: 1.1, color: "#8b7bff", opacity: line(0.6), transform: `translateY(${(1 - line(0.6)) * 60}px)` }}>Two engines.</div>
<div style={{ marginTop: 70 }}>
{BARS.map((bar) => (
<div key={bar.label} style={{ display: "flex", alignItems: "center", height: 84, marginBottom: 22 }}>
<div style={{ width: 200, fontSize: 36, color: "#a1a1aa" }}>{bar.label}</div>
<div style={{ width: bar.width * progress(frame, fps, bar.start, 1.4), height: 56, borderRadius: 12, background: "#8b7bff" }} />
</div>
))}
</div>
<div style={{ position: "absolute", left: 130, bottom: 96, fontSize: 40, color: "#a1a1aa" }}>
Frame <span style={{ color: "#f4f4f5" }}>{frame}</span> of 240
</div>
<div style={{ position: "absolute", right: 130, bottom: 96, fontSize: 40, color: "#a1a1aa" }}>Rendered with Remotion</div>
</div>
</AbsoluteFill>
);
};
Full HyperFrames source (index.html)
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=1920, height=1080" />
<script src="gsap.min.js"></script>
<style>
@font-face { font-family: "Geist"; font-weight: 600; src: url("geist-latin-600-normal.woff2") format("woff2"); }
* { margin: 0; padding: 0; box-sizing: border-box; }
html, body { width: 1920px; height: 1080px; overflow: hidden; }
#root { position: relative; width: 100%; height: 100%; background: #0a0a0b; color: #f4f4f5; font-family: "Geist"; font-weight: 600; }
#all { position: absolute; inset: 0; padding: 110px 130px; }
.line { font-size: 124px; line-height: 1.1; }
#two { color: #8b7bff; }
#bars { margin-top: 70px; }
.row { display: flex; align-items: center; height: 84px; margin-bottom: 22px; }
.label { width: 200px; font-size: 36px; color: #a1a1aa; }
.bar { width: 0; height: 56px; border-radius: 12px; background: #8b7bff; }
.foot { position: absolute; bottom: 96px; font-size: 40px; color: #a1a1aa; }
#count { color: #f4f4f5; }
</style>
</head>
<body>
<div id="root" data-composition-id="main" data-start="0" data-duration="8" data-width="1920" data-height="1080" data-fps="30">
<div id="all" class="clip" data-start="0" data-duration="8" data-track-index="0">
<div id="same" class="line">Same video.</div>
<div id="two" class="line">Two engines.</div>
<div id="bars">
<div class="row"><div class="label">Title</div><div id="b1" class="bar"></div></div>
<div class="row"><div class="label">Bars</div><div id="b2" class="bar"></div></div>
<div class="row"><div class="label">Counter</div><div id="b3" class="bar"></div></div>
</div>
<div class="foot" style="left: 130px">Frame <span id="count">0</span> of 240</div>
<div class="foot" style="right: 130px">Rendered with HyperFrames</div>
</div>
</div>
<script>
const ease = "power2.out"; // the same curve as Easing.out(Easing.cubic) in Remotion
const tl = gsap.timeline({ paused: true });
tl.from("#same", { opacity: 0, y: 60, duration: 0.6, ease }, 0.2);
tl.from("#two", { opacity: 0, y: 60, duration: 0.6, ease }, 0.6);
tl.to("#b1", { width: 1180, duration: 1.4, ease }, 1.5);
tl.to("#b2", { width: 860, duration: 1.4, ease }, 1.7);
tl.to("#b3", { width: 1420, duration: 1.4, ease }, 1.9);
const counter = { frame: 0 };
const count = document.getElementById("count");
tl.to(counter, { frame: 240, duration: 8, ease: "none", onUpdate: () => { count.textContent = String(Math.floor(counter.frame + 0.001)); } }, 0);
tl.to("#all", { opacity: 0, duration: 0.6, ease }, 7.2);
window.__timelines = window.__timelines || {};
window.__timelines["main"] = tl;
tl.seek(0);
</script>
</body>
</html>
Licence and cost
| Question | Remotion | HyperFrames |
|---|---|---|
| Open source? | No. Remotion says it is source-available under its own licence | Yes, Apache-2.0 |
| Free for | Individuals, up to 3 people, qualifying non-profits, evaluation | Everyone |
| Above that | $25 a month per person writing Remotion code, or $0.01 a render with a $100 monthly minimum for automations | No fee |
| Selling on | You may not resell Remotion itself | Apache-2.0 terms |
Read Remotion’s licence FAQ yourself before you rely on this. It is a summary and not legal advice. Prices were read on 2026-11-02. More in our Remotion guide.
With Claude Code
Both tools publish instructions an agent can read. For Remotion, see using it with Claude Code. For HyperFrames, see the HyperFrames guide. You don’t need a Claude plan to use either tool, only to have Claude Code write the video for you.
Which should you pick?
- Pick Remotion if your team already writes React, you want to reuse components, or render time matters most. It is older, and HeyGen’s own page says it has more templates, tutorials and production history.
- Pick HyperFrames if you start from HTML or a website, your company has four or more people and doesn’t want a licence fee, or you want no project install.
- Not sure? Render one of our free skills to see how HyperFrames feels. It is the only engine they use, so they won’t show you Remotion. For something ready-made, try Kinetic Type Title or Data-Chart Video.
What neither does: film anything or make realistic people. For that you need a video model. Can Claude make videos? compares the routes, and open source lists more tools.
Questions
- Is HyperFrames faster than Remotion?
- Not on our test. Remotion took about 16 seconds and HyperFrames about 27 for the same 8-second clip on the same computer. About 12 of HyperFrames' seconds came before its render pipeline began. This is one simple clip of text and bars, so a heavier video may behave differently.
- Is HyperFrames free for commercial use?
- Yes. It is Apache-2.0, with no seat count. Remotion's Free License also allows commercial use, but only for individuals, qualifying non-profits and companies of up to three people.
- Which works better with Claude Code?
- Both publish agent skills for it. We didn't measure which one an agent writes better: Claude Code wrote both versions of this test by hand, from the same brief, and both came out right. HeyGen's own test had Claude Code make a launch video with each tool, with similar times, and HeyGen notes one run decides nothing.
- Can I move a Remotion project to HyperFrames?
- HeyGen publishes a migration guide for that, and says it can't port some patterns, such as logic built on React state. We haven't tried it.
- Does Glidezo use Remotion?
- No. Our free skills use HyperFrames, because of its licence. We rendered Remotion only for this test and our Remotion guide.
Sources
- Remotion licence FAQ: Free License, Creators, Automators (checked 2026-11-02)
- Remotion LICENSE.md on GitHub (checked 2026-11-02)
- Remotion docs: use with Claude Code (checked 2026-11-02)
- HyperFrames on GitHub: README (Apache-2.0, Node.js 22+, FFmpeg) (checked 2026-11-02)
- HeyGen: HyperFrames vs Remotion (HeyGen makes HyperFrames) (checked 2026-11-02)