Build with AI

Rendering video from HTML, with no cloud bill

The Vibe Gate·July 27, 2026·9 min read

Disclosure: there are no affiliate links in this article. The renderer is free and open source, it isn't mine, and nobody paid for this write-up. How this site makes money →

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 lengthRender timeRatio
28 seconds~42 seconds1.5×
43 seconds~1 min 10 sec1.6×
52 seconds~1 min 20 sec1.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.

The honest comparison I can't give you: I never priced this against a cloud render service, because I never hit a wall that made me look. If your batches are hundreds of clips or your deadline is shorter than your laptop, that math changes and you should do it properly. HyperFrames itself can render on Lambda or Cloud Run when you want that. I've simply never needed to leave the machine.

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.

All timings measured on my own 8 GB M1 Mac at 1080×1920 while producing a batch of thirty clips. HyperFrames 0.7.71, Apache 2.0. Software changes; this was accurate on 27 July 2026.

Get the next guide, first.

One email when a new build log or review ships. No noise, ever.