Vanilla static site on Cloudflare Pages
No build step. Edit files → push to main → Cloudflare Pages auto-deploys. Fast,
cheap, and you never fight a toolchain.
Why vanilla (decision rationale)
- Zero build/CI churn; a 2-line copy fix is a 30-second deploy.
- No framework lock-in or rewrite tax. "Add, don't rewrite" is the rule.
- The whole site is greppable, debuggable, and obvious.
- Trade-off accepted: more manual DOM code. Worth it at small/medium scale.
Repo + deploy
- One GitHub repo → Cloudflare Pages project, auto-deploy on push to
main. - Pages serves the repo root. Pretty URLs:
/mi-cuenta.htmlis served at/mi-cuenta. - Functions (serverless) live in
/functions(seesupabase-backend).
The deploy ritual (do this EVERY deploy)
- Edit the files.
- Verify JS compiles — never push broken inline JS. Extract inline
<script>blocks and runvm.compileFunctionon each;node --checkfor.js/.cjs. Seereference/verify-inline.cjs. - Cache-bust the assets:
node .claude/cache-bust.cjs— stamps everyassets/**/*.css|jsreference in the HTML with?v=<git-short-hash>and writesversion.json. Seereference/cache-bust.cjs. - Bump the version stamp (single source of truth in one JS file, shown subtly
in the footer) and log it. See
project-continuityfor the versioning scheme. - Commit (descriptive, in the project's language) with the co-author trailer,
push to
main. - Verify live:
curlthe deployed asset and confirm the new version stamp + thatversion.jsonequals the asset?v=(they must match or the auto-reloader loops). Cloudflare bot-protection blocks plain crawlers → use a browser User-Agent.
.claude/is typically gitignored, so the cache-bust/verify scripts stay local (they run at build time on your machine, not in the repo).
Cache-busting + auto-reload (kills the "no se ve" telephone game)
Two layers, both in reference/:
cache-bust.cjs— re-stamps?v=<git hash>on all asset refs each deploy so browsers re-fetch changed CSS/JS, AND writesversion.json = {"v":"<hash>"}.- Auto-reloader (
reference/auto-reload.js) — a tiny script included on every page that reads its own loaded?v=, polls/version.json(servedno-store), and when a new deploy is detected: reloads on tab-return (safe moment), shows a one-tap "Actualizar" banner while you're looking, and never interrupts mid-typing (window.__noAutoReloadopt-out for exam/checkout flows). Bootstrap caveat: the first time, users hard-refresh once to load the reloader; from then on it's automatic.
Serve the marker fresh via _headers:
/version.json
Cache-Control: no-store, max-age=0
Security headers (_headers at repo root)
Always set: Content-Security-Policy (lock script-src/frame-src/connect-src
to the exact CDNs + your Supabase URL + wss for realtime), X-Frame-Options: DENY,
X-Content-Type-Options: nosniff, Referrer-Policy: strict-origin-when-cross-origin,
Permissions-Policy, Strict-Transport-Security. Gotcha lived: embedding a video
iframe needs the player domain in frame-src or the browser silently blocks it.
Reusable CSS conventions
- Token-based theming with CSS variables; a
data-theme="dark"remap + a sharedlocalStoragekey so the theme syncs across pages/tabs. - Dark mode: contrast-passing is not enough — you also need visible layer hierarchy (bg < surface < card) with light steps + shadow, or it reads as flat.
- Never hardcode
white/colors; use tokens so the theme responds. - Don't break global
.btn/.btn-primary; override in scope.
Files in this skill
reference/cache-bust.cjs— the asset-stamper + version.json writer.reference/auto-reload.js— the deploy auto-reloader (drop into a globally-loaded JS file).reference/verify-inline.cjs— compile-check inline<script>blocks before deploy.reference/headers-example— a starter_headers.