$0 forever No build step Mobile-first
Scaffold
A DRY, replicable system for building mobile-first sites on Cloudflare’s free tier. One template, one scaffold script, one deploy path — every new site starts identical and stays cheap forever.
The Infra
Every piece of the stack, what it does, and what the free tier gives you. Nothing here costs a cent.
Tap the architecture
One diagram, the whole system. Tap any node to see what it does.
Tap a node
Each box is one billable thing that bills $0. Pick one to see its job.
Cloudflare Pages
Hosts the static site — HTML, CSS, JS — on a global CDN. This is the default home for every Scaffold site.
Pages Functions / Workers
Server code only where a site truly needs it: the chat proxy, the agent dispatcher, secret-key calls. Everything else stays static.
KV
Tiny key-value reads at the edge. Chat transcripts, caches, feature flags — anything small and read-heavy.
D1
SQLite for structured state that needs queries and strong consistency — agent task records, app data with relations.
R2
Object storage for media: photos, uploads, files too big or too binary for a database.
OpenAI-compatible LLM API
The brains: chat replies and agent reasoning through one server-side key. Any OpenAI-compatible endpoint plugs in.
Brave Search API
Gives the agent live web research: the Function calls search, hands concise results back to the model.
Limits: Pages + Functions from FRAMEWORK.md. KV/D1 figures are 2026-09 verified values (see the meter below). R2/LLM/Search allowances live with their providers — check at setup.
Patterns
Two battle-tested recipes. Copy the shape, not the code — each site owns its copy.
Data-driven static
No backend
A scheduled publisher regenerates data/*.json; the site renders it. Scores, reports, rosters — anything that changes on a schedule but reads the same for everyone.
# publisher (cron): atomic write, then deploy write("data/site.json.tmp", json) rename("data/site.json.tmp", "data/site.json") git push # Pages rebuilds; data + site update together // js/app.js — the whole "backend" const data = await getJSON("data/site.json"); renderCards(data); // zero Functions, zero DB
Rules: atomic writes, versioned JSON per deploy, publisher failures never delete the last good file.
LLM chat
Drop-in widget
Multi-model chat with one shared conversation: the widget has a model picker, every model reads and writes the same transcript, context, and progress. The key never leaves the server.
// POST /api/chat → OpenAI-compatible SSE stream await fetch("/api/chat", { method: "POST", body: JSON.stringify({ conversation_id: id, model, message }) }); // server: allowlisted model id → upstream → stream → persist
Ships research-first: the default system prompt checks existing ideas (GitHub, Reddit, HN) before proposing, and never invents citations.
Free-tier meter
Will it stay $0? Punch in your numbers — the bars show usage against the free limits. Rough planning heuristics, not measured figures.
Limits hardcoded as 2026-09 verified values. Static page views ride unlimited bandwidth — they cost nothing.
Decide
The framework’s judgment, as tools. Answer honestly.
Do I need a Pages Function?
KV vs D1 vs R2 vs static JSON?
Ship-it checklist
Run this before every deploy. Your ticks are saved on this device.
Sites
Apps built the Scaffold way. Every one started as the same template.
Moves
In development
Mobile fantasy-football companion: waiver-target cards, positional roster view with injury status, and a static site.json data flow — the data-driven-static pattern in production.
No public URL yet.
Scaffold
You are here
The framework’s own home: infra map, patterns, free-tier meter, decision tools, and the Builder. Static, except the Builder’s one API.
This page.
Start a site
One command. Thirty seconds to live.
# 1. Scaffold ./bin/new-site my-site "My Site" # 2. Develop cd sites/my-site && python3 -m http.server 8000 # …edit index.html / css/app.css / js/app.js… # 3. Deploy — connect the Pages project to Git once, # then every push to main is live in ~30s. git push origin main
The DRY contract
1. One template. Every site begins as a copy of templates/mobile-site/. Shared improvements go into the template — never copy-pasted between sites.
2. No build step by default. Plain HTML/CSS/JS deploys as-is. Add a bundler only when a site outgrows it — most never will.
3. Mobile-first is encoded, not remembered. Viewport, safe areas, 44px targets, 100dvh, dark mode — all in the template’s tokens. New sites inherit them.
4. One deploy path. Git-connected Pages project: push to main → live. wrangler pages deploy is the fallback, not a second workflow.
5. Static until proven dynamic. Data-driven sites publish JSON; Pages Functions cover the rest. No servers, no containers, nothing to keep awake.
Builder
Prompt the agent. It builds a new app or a feature on an existing one, then lands it as a pull request with a live preview — you approve before anything merges.