https://github.com/heygen-com/hyperframes-cloudflare-template
HyperFrames template for Cloudflare Workers + Containers — preview HTML video compositions in the browser, render MP4s server-side.
https://github.com/heygen-com/hyperframes-cloudflare-template
cloudflare-containers cloudflare-workers hyperframes r2 template video-rendering
Last synced: 12 days ago
JSON representation
HyperFrames template for Cloudflare Workers + Containers — preview HTML video compositions in the browser, render MP4s server-side.
- Host: GitHub
- URL: https://github.com/heygen-com/hyperframes-cloudflare-template
- Owner: heygen-com
- License: apache-2.0
- Created: 2026-04-28T07:05:33.000Z (3 months ago)
- Default Branch: main
- Last Pushed: 2026-05-07T21:36:28.000Z (2 months ago)
- Last Synced: 2026-05-07T23:33:04.158Z (2 months ago)
- Topics: cloudflare-containers, cloudflare-workers, hyperframes, r2, template, video-rendering
- Language: HTML
- Homepage: https://hyperframes-on-cloudflare.jdrusso1020.workers.dev
- Size: 362 KB
- Stars: 2
- Watchers: 0
- Forks: 1
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
# HyperFrames on Cloudflare
[](https://deploy.workers.cloudflare.com/?url=https://github.com/heygen-com/hyperframes-cloudflare-template)

**Live demo:** [hyperframes-on-cloudflare.jdrusso1020.workers.dev](https://hyperframes-on-cloudflare.jdrusso1020.workers.dev)
A [HyperFrames](https://github.com/heygen-com/hyperframes) template that previews HTML video compositions in the browser and renders MP4s server-side using a [Cloudflare Container](https://developers.cloudflare.com/containers/) (Chromium + FFmpeg) and stores them in [R2](https://developers.cloudflare.com/r2/).
Demonstrates Worker-to-Container fetching via Durable Object bindings, streaming response bodies through the Worker into R2, and bundling sub-compositions into a single self-contained preview HTML at build time.
Deploying provisions a Worker, the `RenderContainer` Durable Object, and an R2 bucket (`hyperframes-renders`). Cloudflare Containers requires a [Workers Paid](https://developers.cloudflare.com/workers/platform/pricing/) plan.
## What this template does
- **Preview** a bundled composition (`cloudflare-intro`) in the browser using ``, the zero-dependency web component from `@hyperframes/player`.
- **Render** the composition to an MP4 by POSTing to `/api/render`. The Worker streams the composition to a Cloudflare Container running a pre-built image with Chromium + FFmpeg + HyperFrames, streams the rendered MP4 directly into R2, and returns a URL.
- **Generate from a prompt (BYOK)** — paste an OpenRouter API key and a text prompt; the Worker calls OpenRouter (Gemini 3 Flash by default) to synthesize a HyperFrames composition, lints it with `@hyperframes/core/lint`, self-heals up to 2× if needed, and previews the result in the player. Click "Render MP4" to capture it. Off by default — see [AI generation](#ai-generation-byok).
**Authoring happens locally.** This template ships with one pre-authored composition. To build your own, use the HyperFrames CLI on your machine:
```bash
npx hyperframes init my-video
cd my-video
npx hyperframes preview # live-reload editor in your browser
```
Then swap it into this template (see [Swapping the composition](#swapping-the-composition) below).
## Architecture
```
Browser Worker Container DO (instance_type: standard-4)
┌──────────────────┐ ┌────────────────────────┐ ┌──────────────────────────────────┐
│ │ │ - load files from │ │ - writes files to /tmp/ │
│ preview iframe │ │ ASSETS │ │ - hyperframes render │
│ │ │ - POST → container │ │ (Chromium + ffmpeg) │
│ │ ◀──── │ - stream → R2 bucket │ ◀──── │ - streams mp4 in response │
│ │ url │ - return /r/ │ mp4 │ │
└──────────────────┘ └────────────────────────┘ └──────────────────────────────────┘
│
├─▶ R2 (hyperframes-renders)
│
└─▶ ASSETS (preview HTML, composition files)
```
### The container image
Cold-start of a render container is faster than installing dependencies on every request because the renderer is **baked into the image** at build time, not installed at runtime:
1. `node:22-bookworm-slim` base
2. `apt-get install` Chromium system libs (`libnss3`, `libxcomposite1`, `pango`, …)
3. `npm install hyperframes ffmpeg-static`
4. Symlink `ffmpeg-static/ffmpeg` to `/usr/local/bin/ffmpeg`
5. `npx hyperframes browser ensure` to download `chrome-headless-shell`
6. Copy `container/server.mjs` (a small Node HTTP server) and `CMD ["node", "server.mjs"]`
At render time, the Worker sends composition files in the request body, the container writes them to a tmp dir, runs `hyperframes render`, and streams the MP4 back. Container instances sleep after 10 minutes of inactivity (`sleepAfter` on the Container class).
### Why Cloudflare Containers (and not Browser Rendering)
Cloudflare's [Browser Rendering](https://developers.cloudflare.com/browser-rendering/) is a hosted Chromium API — great for screenshots and PDFs, but you can't install FFmpeg into it. HyperFrames needs full control of the Chromium process plus an FFmpeg binary on the same filesystem, which is exactly what [Cloudflare Containers](https://developers.cloudflare.com/containers/) gives you: an OCI container in a Worker-bound Durable Object, with up to 4 vCPUs and 12 GiB of RAM on `standard-4`.
With 4 vCPUs, `hyperframes render --workers auto` launches 3 parallel Chrome workers, cutting the render time roughly 2× vs. the single-worker default.
## Local development
```bash
npm install
npm run dev
```
`wrangler dev` runs the Worker locally and builds + runs the container against your local Docker daemon (Docker is required for local container dev). The browser preview works without Docker; only `/api/render` needs the container.
### Testing the render container in isolation
If you want to iterate on the `Dockerfile` or `container/server.mjs` without booting Wrangler, you can hit the container directly:
```bash
docker build -t hf-render .
docker run -d --rm --name hf-test -p 18080:8080 hf-render
node scripts/test-render.mjs 18080 /tmp/out.mp4
docker stop hf-test
```
The script reads `src/composition-manifest.json`, base64-encodes the composition files, POSTs them to the container, and writes the MP4 it returns. The bundled 9s composition renders in ~17s on a 6-vCPU host.
## Project structure
```
src/
index.ts # Worker entry — preview + /api/render + /r/
container.ts # RenderContainer Durable Object
composition-manifest.json # Generated by scripts/build.mjs
container/
server.mjs # Node HTTP server inside the container
package.json # Container deps (hyperframes + ffmpeg-static)
public/
index.html # Preview UI + Render button
compositions/
cloudflare-intro/ # The bundled example composition
index.html
assets/ # Cloud SVG + HF icon used by the intro
scripts/
build.mjs # Run via wrangler.jsonc → build.command
bundle-preview.ts # Bundles composition into single HTML via @hyperframes/core
test-render.mjs # Local container E2E
Dockerfile # Render container image
wrangler.jsonc # Worker + Container + R2 bindings
```
## Swapping the composition
1. Drop your composition bundle into `public/compositions//`.
2. Set `PREVIEW_COMPOSITION_DIR` env var when running build/deploy:
```bash
PREVIEW_COMPOSITION_DIR=compositions/ npm run deploy
```
Or edit the default in `scripts/build.mjs` (line 16).
3. Optionally update the player dimensions in `public/index.html` if your composition isn't 1920×1080.
4. Re-run `npm run dev` or `npm run deploy` — `scripts/build.mjs` regenerates the manifest and bundle.
## AI generation (BYOK)
The "Generate from a prompt" panel lets a viewer paste their own OpenRouter API key, type a description, and synthesize a HyperFrames composition end-to-end. The composition previews in the player; the Render button then captures it to MP4 just like the bundled one.
### Enabling it
It's already on for self-deployers — `wrangler.jsonc` sets `ENABLE_AI_GEN: "true"` in `vars`. Set it to `"false"` if you're hosting a public demo and don't want visitors triggering renders on your account.
### How the API key is handled
- The user pastes their key into the panel; it's sent in the body of `POST /api/generate`.
- The Worker forwards the key once to `https://openrouter.ai/api/v1/chat/completions` as `Authorization: Bearer `.
- The Worker does not log, cache, or persist the key. It exists only for the duration of one request.
- Client-side, the key is mirrored to the tab's `sessionStorage` so generate→edit→regenerate doesn't require pasting it every time. Closing the tab clears it.
### Pipeline
```
prompt + key
│
▼
POST /api/generate (Worker)
│
├─▶ build skill prompt (src/lib/hyperframes-skill.ts)
├─▶ fetch openrouter.ai (Gemini 3 Flash by default)
├─▶ lintHyperframeHtml(html) (@hyperframes/core/lint)
├─▶ if lint fails, retry up to 2× with feedback
└─▶ return { html, model, attempts, lintOk, lintErrors }
frontend
│
└─▶ player.setAttribute("srcdoc", html) (no Blob URL needed)
POST /api/render { html } (existing endpoint, now accepts inline HTML)
│
└─▶ container → MP4 → R2 → /r/
```
The default model is `google/gemini-3-flash-preview` — cheapest and fastest direct generation per ~80 eval runs in [llm-stories-hyperframes](https://github.com/jrusso1020/llm-stories-hyperframes), which the prompt is adapted from. You can pass a different `model` field in the request body to swap in any [OpenRouter model](https://openrouter.ai/models).
## Pricing
[Cloudflare Containers pricing](https://developers.cloudflare.com/containers/pricing/) — pay-per-10ms for memory, CPU, and disk. A 70-second render on `standard-4` (4 vCPU, 12 GiB) costs ~$0.008. R2 storage is $0.015/GB-month with no egress fees within Cloudflare's network.
## License
[Apache-2.0](./LICENSE) — same license as HyperFrames itself.
## Links
- [HyperFrames repo](https://github.com/heygen-com/hyperframes)
- [HyperFrames docs](https://hyperframes.heygen.com)
- [Cloudflare Containers docs](https://developers.cloudflare.com/containers/)
- [Cloudflare R2 docs](https://developers.cloudflare.com/r2/)