HyperFrames: HTML to MP4 on Your Own VPS, Tested
HyperFrames turns plain HTML into MP4 on your own server. I tested it on an 8-core VPS: real render times, memory use, byte-identical repeats, and the two gotchas that bite on day one.
Three identical MD5 hashes. I rendered the same 8-second video three times on our VPS and got byte-identical files each time, with no editor in a browser tab, no upload and no render credits: just an HTML file, headless Chrome and FFmpeg on a box I already pay for.
The tool was HyperFrames, an open-source project from HeyGen that turns plain HTML into MP4. It picked up 5,883 stars on GitHub this week and sat 8th on the weekly trending list. I wanted to know if it holds up on a normal server, so I installed it and measured everything.
Video rendering is quietly becoming another subscription
Many of us moved email, storage and CI back onto our own servers to get away from monthly bills that kept stacking up. Video has been drifting the other way. Simple jobs like a title card, a weekly stats clip or a product teaser now often live inside a cloud editor that charges per render or per seat.
That is fine for a one-off. It hurts when you generate 30 short clips a month for a client and every one of them goes through someone else's queue. HyperFrames puts the renderer back on your own box: HTML in, MP4 out, no per-render fee. The docs say local rendering is free and does not use HeyGen credits.
What HyperFrames actually is
The HyperFrames GitHub repo uses the Apache-2.0 license, is written in TypeScript and was created on 10 March 2026. On 19 October 2026 it had 66,870 stars and 6,064 forks. The npm package, also called hyperframes, is on version 0.8.174 and had 525,652 downloads in the week of 10 to 16 October.
The idea is simple. You write a page in HTML and CSS and mark when things appear with data-* timing attributes. HyperFrames then drives a headless browser (Chrome running with no window, controlled by code) to capture every frame, and FFmpeg stitches those frames into a video. There is no build step and no framework to learn first.
Your animations need to be seekable. That means the tool can jump to any exact moment, say 4.5 seconds, and get the same picture every time. A GSAP timeline works well for this, so that is what I used.
Installing it on a plain VPS
My test machine was an 8 vCPU AMD EPYC VPS with 23.5 GB of RAM, 149 GB of free disk and no GPU. The README asks for Node.js 22 or newer plus FFmpeg. The quickstart docs cover the rest well.
The first useful command is hyperframes doctor. It told me Chrome Headless Shell was missing, so I ran npx hyperframes browser ensure, which downloaded chrome-headless-shell v152.0.7977.30 (113.9 MB). Doctor also listed some optional local add-ons: whisper-cpp for transcription, Kokoro TTS for voiceover and MusicGen for music. I skipped those for this test.
Here is the whole flow I used, in one block. You can run the last line first if you like, and I explain why further down.
npx hyperframes doctor
npx hyperframes browser ensure
npx hyperframes init testvid --non-interactive --example blank
cd testvid
npx hyperframes lint
npx hyperframes render
npx hyperframes telemetry disableI replaced the blank example with my own 8-second 1920x1080 composition: a title, a subtitle, a progress bar, a spinning square and a counter, all driven by one GSAP timeline. Lint returned 0 errors and 0 warnings on the first try.
The render: numbers from three runs
I rendered the same composition three times at 1080p and 30 fps. The wall clock times were 54.8 seconds, 53.1 seconds and 48.1 seconds. Peak memory was 657,932 KB, about 643 MiB, which a modest VPS can easily handle.
One run printed its own breakdown: compile 10.7s, setup 3.5s, capture 38.5s with encoding running alongside, and assemble 0.1s. Capture used "beginframe", a Chrome mode where the renderer requests each frame instead of recording in real time. It used a software GPU because this box has no real one.
The output was H.264, yuv420p, 1920x1080 at 30 fps, and just 740.4 KB. All three files had the same MD5 hash, 1c487c7203a2a7c503e82f9cd7d82beb, and the frames I pulled at 0.5s, 2.0s, 4.5s and 7.9s were byte-identical. Same input, same file. If you cache outputs or diff them in CI, that is a big deal.
Gotcha 1: a "local" render with a remote dependency
The scaffolded index.html loads GSAP 3.14.2 from cdn.jsdelivr.net. So your self-hosted render quietly depends on a third-party CDN being up and serving the same file tomorrow.
The fix took two minutes. I downloaded the GSAP file into the project, pointed the script tag at the local copy and rendered again. The output stayed byte-identical to the CDN version, so vendoring the library costs you nothing. Do it on day one.
Gotcha 2: telemetry is on by default
On a fresh install, npx hyperframes telemetry status printed "Status: enabled". The telemetry is anonymous, but on a client server I want to make that call myself, not find out about it later.
Turn it off with npx hyperframes telemetry disable, or set HYPERFRAMES_NO_TELEMETRY=1 or DO_NOT_TRACK=1. In Docker or systemd, put the variable in the service definition so every render inherits it.
HyperFrames vs Remotion
Remotion is the tool most people compare it with, and HeyGen publishes its own HyperFrames vs Remotion guide. Both drive headless Chrome and FFmpeg underneath. The real differences are how you write videos and which license you accept.
| Point | HyperFrames | Remotion |
|---|---|---|
| Authoring | Plain HTML with data-* timing | React components |
| Build step | None | Bundler required |
| License | Apache-2.0 | Source-available, own license |
| Render engine | Headless Chrome plus FFmpeg | Headless Chrome plus FFmpeg |
We already run a home-grown pipeline for our daily short videos: headless Chromium screenshots plus FFmpeg. So this is familiar ground. HyperFrames gives you structure, linting and one repeatable render command. A home-grown script gives you total control and one less dependency. Both are valid choices.
Two features stood out for client work. The --variables flag merges JSON values into a composition. The --batch flag takes a JSON array and renders one video per row, so 50 personalised clips can come from a single template. There are also resolution presets, including portrait 1080x1920 and 4K.
Where it is the wrong tool
About 55 seconds for 8 seconds of video is fine for scheduled content. If a cron job builds tomorrow's clip overnight, nobody cares. It is painful for interactive use, where a user clicks a button and waits, and slow for bulk jobs of hundreds of videos.
Average CPU use was 117 percent, roughly one and a bit of my eight cores, because capture runs through a single browser. A bigger server will not make one render much faster. Running several renders in parallel is likely the better bet.
It also needs a full browser on the box, which means more disk and more patching. There is a --gpu flag, off by default, and --low-memory-mode switches on automatically at 8 GB of RAM or less. If you outgrow one box, it supports Docker, AWS Lambda, Google Cloud Run and a paid cloud render.
Frequently asked questions
Is HyperFrames really free to run locally?
Yes. It is Apache-2.0, and the docs say local rendering does not use HeyGen credits. You only pay for your own server.
Do I need a GPU?
No. My test box had no GPU and rendered 8 seconds of 1080p in 48 to 55 seconds using software rendering. The --gpu flag exists but is off by default.
Will it run on a small VPS?
Probably. Peak memory in my test was about 643 MiB, and low-memory mode turns on automatically at 8 GB of RAM or less. You still need Node.js 22, FFmpeg and about 114 MB for the headless browser.
Should I replace my own Chromium and FFmpeg script?
Only if you want structure, linting and a standard render command. If your script works and you like owning every line, there is no rush to change it.
Written by Hitesh Saini. I installed HyperFrames on our own VPS, and every number in this post was measured in this test. The drafting was AI-assisted, then reviewed and edited by hand.
Want a self-hosted video pipeline like this set up on your own server? See our services page.