I make short vertical videos — product reels, news explainers, a tour of this website. Thirty of them came out of one long session recently, and the total infrastructure spend for that session was zero.
Not "cheap." Zero. No render minutes, no per-second billing, no queue, no upload. The machine is an 8 GB M1 Mac, which is the entry-level laptop from several years ago, and it did all thirty without complaining.
Here's how that works, what it actually costs in wall-clock time, and — the part I'd want if I were reading this — the seven ways it fails that don't look like failures.
The idea: the video is a web page
The tool is HyperFrames, an open-source (Apache 2.0) renderer from HeyGen. I'm on 0.7.71. It takes HTML, CSS and a GSAP timeline and gives you back an MP4.
If you've used Remotion, the shape is familiar: describe motion in code, render deterministically, get a file. The difference that mattered to me is where it runs. There's no service in the loop by default. You point it at a folder and it renders on your own CPU.
What you gain from a page-based renderer is everything the web already does well and video tools do badly. Layout is CSS grid. A "template" is a file with variables in it. Restyling forty clips is a stylesheet edit. Version control is git, and a diff is readable.
The best thing about video-as-HTML isn't the rendering. It's that a video becomes a text file, and text files are things a script can generate a hundred of.
That's exactly what I do with it — a JSON file of products feeds a template, and the build script emits one MP4 per row.
What it actually costs in time
Everyone quotes the price and nobody quotes the wait, so here are my numbers, off my own machine, all at 1080×1920:
| Output length | Render time | Ratio |
|---|---|---|
| 28 seconds | ~42 seconds | 1.5× |
| 43 seconds | ~1 min 10 sec | 1.6× |
| 52 seconds | ~1 min 20 sec | 1.5× |
So roughly one and a half times the runtime of the video, consistently, on 8 GB of RAM. A batch of sixteen thirty-second reels is about fifteen minutes of the laptop being busy. You go make coffee. You don't go check a dashboard.
Horizontal output defaults to 720p, and for social crops that's been fine. I haven't needed 4K, so I can't tell you how that scales — assume worse than linear and test it before you promise anyone a deadline.
Seven failures that render "successfully"
This is the section I wish someone had written before I started, because every one of these produced a file. No crash, no error, just a wrong video.
1. Right-to-left on the html element gives you a completely black video. Setting dir="rtl" on <html> — which is the correct thing to do on a web page — produces pure black frames. Every frame. The fix: keep lang="ar" on the root, and put direction on the elements that need it instead.
/* not on <html> — on the elements */
.rtl { direction: rtl; }
2. Numbers inside a right-to-left sentence come out reversed. Mixed-direction text is a browser problem, not a renderer problem, but it shows up in the output and you may not read the language well enough to notice. Isolate them:
.num {
unicode-bidi: isolate;
direction: ltr;
display: inline-block;
}
3. Fonts loaded from an external stylesheet are silently ignored. A <link> to a CSS file containing your @font-face rules doesn't get honoured by the renderer's static guard, so the page renders in a fallback font. On screen in your browser it's perfect. In the MP4 it's Helvetica. Put @font-face inside the page itself.
4. A full-bleed background on the root element disappears at composite time. Setting a background on #root gets dropped. Put it on an absolutely-positioned child with inset: 0 and it survives.
5. Two clips on the same track that overlap by any amount is a hard error. Not a warning, not last-one-wins. This is the one that bit me with rounding — sentence timings rounded to two decimals can produce boundaries that disagree by a millisecond, which is enough. Use three decimals, or put the clips on separate tracks.
6. Light text over a bright image fails the contrast check. The validator enforces WCAG AA on text, which I initially found annoying and now think is correct — a caption that's unreadable over a white product photo is a broken video, it just breaks for the viewer instead of for you. Fix it with a gradient scrim that darkens the edges rather than by lightening the text.
7. Fading something out at a scene boundary makes it reappear when you seek. A GSAP tween to opacity: 0 that ends exactly at a boundary leaves the element visible if the renderer jumps into the timeline at that point rather than playing through it. Follow every such tween with an explicit set:
tl.to(el, { opacity: 0 }, sceneEnd - 0.4);
tl.set(el, { opacity: 0 }, sceneEnd); // or it comes back
The habit that catches five of the seven
There's a validation step you can run before rendering, and I run it every time now:
npm run check # before every render, no exceptions
It caught numbers 1, 3, 5, 6 and 7 on real builds — each of which would have shipped as a finished-looking file with something quietly wrong in it. Fifteen seconds of validation against a minute of rendering plus however long it takes you to notice a font substitution.
The other habit matters just as much, and it's cruder:
ffmpeg -i out.mp4 -ss 00:00:06 -frames:v 1 frame.png
"Render complete" does not mean "correct." Pull a frame out of the middle and look at it with your eyes. This is doubly true if the video is in a language or script you don't read fluently — the checker validates contrast and timing, not whether the sentence makes sense.
Getting real design in, instead of inventing it
The feature I underrated at first: it can read an existing website and hand you its actual design.
npx hyperframes capture <url> -o ./capture
That pulls scroll screenshots, the real TTF font files, SVG logos, and a tokens.json of the site's colours. So when I built a tour video for this site, none of the brand colours were eyeballed and no font was "close enough" — every visual in it is cut from the real page.
It's a small thing that removes a whole category of embarrassment. Brand video that's slightly the wrong purple is worse than no brand video.
Where this doesn't belong
Two limits I've hit, so you don't have to discover them the hard way.
It can't record another application. If your video needs a screen recording of someone using a tool, this is the wrong instrument — it renders a page you wrote, not a session you performed. Capture that separately and composite it in.
It's not a replacement for an existing pipeline that works. I have longer-form work that runs on Remotion and I'm not migrating it. This is for new short-form: reels, shorts, site tours, anything templated. Rewriting a working renderer to be on the newer thing is a hobby, not a decision.
Would I recommend it
If you write CSS and you need more than about five videos of the same shape, yes, without much hesitation. The learning curve is a weekend and most of it is the seven items above.
If you need one beautiful video and you already know an editor, open the editor. Building a generator to produce a single artifact is the most common way I've seen people waste a week.
The reason I'm still on it is narrower than "it's free." It's that a video I can generate from a data file is a video I can regenerate when the data changes — new price, corrected number, better script — without opening anything. The zero-dollar render bill is real, and it's the second-best thing about it.