Ad-Hoc Web Deploy Primer
Quickly deploying bespoke pages to the live web — what your options are, what they cost, how to keep them safe, and how to do it in 60 seconds once you're set up.
1 · TL;DR — the one-paragraph version
For ad-hoc bespoke web pages, Cloudflare Pages is the right default: unlimited bandwidth, free SSL, free CDN, free custom domains, free preview deploys, edge functions for dynamic stuff. The total monthly cost for personal-scale projects is $0 — your only spend is domain registration (~$9–30/yr per domain) plus an optional $5/mo Workers Paid plan if you blow past 100k Function invocations/day. Get good at: (a) npx wrangler pages deploy from CLI, (b) connecting a GitHub repo for declarative deploys, (c) functions/_middleware.js for password gates, (d) Cloudflare Access for real auth, (e) R2 for media. That's the whole game.
2 · Platform comparison
The "who hosts my HTML" decision. Below: the platforms that matter, ranked by fit for ad-hoc bespoke work.
Static hosting + edge Functions on Cloudflare's CDN. What you deployed tonight.
Made by the people who built Next.js. Optimized for React/Next workflows.
The original Jamstack platform. Battle-tested, great auth/forms/Identity built in.
Pure static. Deploy by pushing to a branch. No backend, no auth, no preview deploys.
OG one-command static deploy. surge ./dist and you have a URL.
foo.surge.sh).
A CDN, not a hosting platform — useful as a media side-car for any platform above.
The "I want full control and don't mind 30 pages of config" option.
1. Cloudflare Pages · 2. Vercel · 3. Netlify · 4. GitHub Pages · 5. Surge.sh. AWS is overkill unless you're hitting enterprise scale or compliance requirements.
3 · Costs — what you actually pay
The "free forever" zone (Cloudflare Pages free tier)
| Resource | Free tier limit | What it means for you |
|---|---|---|
| Bandwidth | Unlimited | No cap. Your Mother's Day site could serve 10,000 visitors a month and still be $0. |
| Requests/mo | Unlimited | Same — unmetered. |
| Builds | 500/month | You'd need to deploy 16 times/day to hit this. |
| Files per deploy | 20,000 | Plenty for any sane static site. |
| File size | 25 MB | Tight for video; fine for HTML/images. Use R2 for big files. |
| Custom domains | 100 per project | You can have foo.loudog.uno, bar.loudog.uno, ... all in one project. |
| Function invocations | 100,000/day | ~3M/month. Plenty for password-gated sites or light APIs. |
| Worker CPU time | 10ms per request | Generous for HTML/JSON. Heavy compute needs paid. |
| R2 storage | 10 GB/mo free | Plenty for personal photos / media archives. |
| R2 egress | Free (always) | The killer feature. Serve all the photos you want, no bandwidth bill ever. |
| D1 (SQLite) | 5 GB free, 5M reads/day | Solid for any personal app DB. |
| KV (key-value) | 100k reads/day, 1k writes/day | For session tokens, feature flags, etc. |
| Access (auth gate) | 50 users free | Real SSO/email-OTP for free. Per-user audit log included. |
What you'd pay if you exceed free tier
| Tier / item | Cost | When you'd hit it |
|---|---|---|
| Workers Paid plan | $5/mo | >100k Function calls/day OR >10ms CPU. Personal site never hits this. |
| Beyond Workers Paid | $0.30 per 1M req | Only after 10M req/mo on paid plan. |
| R2 storage beyond 10GB | $0.015/GB/mo | 1TB = $15/mo. |
| R2 Class A (writes) | $4.50 per million | You'd need to upload millions of files. |
| R2 Class B (reads) | $0.36 per million | Pricing here means R2 wins for any media site. |
| Cloudflare Access >50 users | $3/user/mo (Zero Trust Standard) | Family sites stay free here. |
| Domain registration | at-cost, no markup | .com ≈ $10/yr · .uno ≈ $30/yr · .dev ≈ $13/yr |
The classic horror story is egress fees on S3 or Vercel bandwidth caps. A viral blog post can cost $200 on AWS overnight. Cloudflare's R2 + Pages combo eliminates this risk entirely — bandwidth is unmetered. If you stay on CF, the only way to get an unexpected bill is to invoke Workers very heavily on the paid plan.
Real-world cost projection — Lou's hub vision
If loudog.uno/share/{person}/{project} hosts 10 personal projects with ~500 visitors/mo each and 10GB of total media:
| Item | Monthly cost |
|---|---|
| Cloudflare Pages hosting | $0 |
| R2 storage (10GB) | $0 (under free tier) |
| R2 egress | $0 (always free) |
| Functions for routing/auth | $0 (well under 100k/day) |
| Cloudflare Access for 5 family members | $0 (under 50) |
Domain (loudog.uno) — annualized | ~$2.50/mo |
| Total | ~$30/year |
4 · Deploy methods — the 5 levels of agency
Netlify Drop (drop a folder onto netlify.app/drop), Surge (surge ./dist). Zero config. Best for one-off "send Mom a thing" deploys. No version history, no API access. 30 seconds to live URL.
wrangler pages deploy, vercel deploy, netlify deploy. Scriptable. State lives on your machine. Great for "I'm building a site right now and want to iterate fast." This is what you did tonight. 5–60 seconds per deploy after one-time auth.
Connect a GitHub repo to Pages/Vercel/Netlify. Every push auto-deploys. Each branch gets a preview URL. PR comments show preview links. This is the gold standard for any project you'll touch more than once. Add it once, never run a deploy command again. 20–60 seconds per push (build time).
Define your CF setup (DNS, Pages projects, R2 buckets, Workers, Access policies) in version-controlled config. terraform apply reconciles state. Overkill for personal stuff; essential for teams. Setup: hours. Operation: instant + auditable.
You say "spin up X" → agent generates site → deploys → returns URL. Requires: broad CF API token in env, optionally the Cloudflare MCP server. Tonight we did Level 2 with a Level 5 wrapper. One sentence → minutes to live.
For throwaway / one-off: stay at Level 2 (CLI). For anything you'll touch twice: move to Level 3 (GitHub-connected). For infra you really care about (your main loudog.uno setup): Level 4 (Terraform). Level 5 wraps all of these — it doesn't replace them.
5 · The supporting stack
HTML hosting is one piece. Real sites need more. Here's the full Cloudflare stack — you can mix-and-match per project.
| Role | Cloudflare option | When to use |
|---|---|---|
| Static HTML / SPA | Pages | Always your starting point |
| Server-side logic | Pages Functions (inside Pages) or Workers (standalone) | Auth, API endpoints, dynamic rendering |
| Large file storage | R2 | Photos >100KB, video, archives. Use signed URLs for private files. |
| Structured data | D1 (SQLite) | User accounts, blog posts, anything relational |
| Key-value cache | KV | Session cookies, feature flags, rate limit counters |
| Real-time state | Durable Objects | WebSockets, presence, collaborative editing |
| Queue jobs | Queues | Background tasks, retries, fan-out |
| AI inference | Workers AI | Llama, embeddings, image gen at the edge |
| Auth gate | Access (Zero Trust) | Real user auth via email-OTP, GitHub, Google SSO |
| DNS / Registrar | Cloudflare Registrar | Cheapest at-cost domains, integrates with everything above |
| Observability | Analytics (built into Pages) | Free, privacy-preserving, no cookies |
An "expert" site recipe for the loudog.uno hub:
// Mom hub at loudog.uno/share/mom/ Cloudflare Registrar → loudog.uno # $30/yr Cloudflare Pages → loudog-uno project # static HTML + JSON index Pages Functions → routing, signed URLs # dynamic mom/* routes Cloudflare R2 → media bucket # photos, videos, audio Cloudflare Access → email-OTP gate # mom + 5 family emails D1 → site_metadata.db # content index, when added
6 · Security
The three privacy levels for the URL you ship
No auth. Anyone with the URL sees everything. Get indexed by Google. Good for: marketing, public docs, open portfolio. What this primer doc is on.
One shared password. Anyone who has it gets in. Cookie persists. Easy to share but also easy to leak. Add robots.txt Disallow + <meta noindex> so search engines don't crawl it. Good for: family albums, draft work, internal-team-only. What the Mother's Day site uses.
Per-user identity. Email-OTP, GitHub, Google login. Audit log shows who saw what. Revoke per user. Good for: anything with PII, internal tools, paid content. Free for <50 users. The right answer for the loudog.uno/share hub.
Token hygiene (what you're setting up right now)
- Scope minimally. A token that only needs to deploy Pages shouldn't have DNS edit. One token per use-case if you can stomach it.
- Rotate. Quarterly is fine. Cloudflare tokens are immortal until you delete them.
- Store outside of repos.
~/.zshrc,~/.config/cloudflare/tokenwithchmod 600, or 1Password. Never in.envcommitted to git. - Use IP filtering when you can. CF token UI offers "Client IP Address Filtering" — restrict to your home IP if you're not traveling.
- Audit logs. Dashboard → Manage Account → Audit Logs shows every token-authed action. Check after suspicious activity.
The "robots.txt is not security" reality check
Some agents and crawlers ignore robots.txt. Internet Archive (Wayback) historically respected it; some AI scrapers don't. If the content can't ever be seen by strangers, use real auth. robots.txt + URL obscurity is "soft" privacy at best.
Headers and CORS
Cloudflare Pages defaults are pretty open. If your project has data endpoints or scripts that you don't want loaded from other domains, add a _headers file at the root of your deploy:
# _headers
/*
X-Frame-Options: DENY
X-Content-Type-Options: nosniff
Referrer-Policy: strict-origin-when-cross-origin
Content-Security-Policy: default-src 'self'
/api/*
Access-Control-Allow-Origin: https://loudog.uno
7 · Workflow patterns
Pattern P1 · Throwaway one-off ("here, look at this")
- Write
index.htmlin a scratch dir npx wrangler pages deploy . --project-name=temp-$(date +%s)- Get URL, share, forget
- Delete project when done
Best for: ephemeral demos, prototypes, "remember when I showed you that thing?"
Pattern P2 · Iterative dev with previews
- Create GitHub repo for the site
- Cloudflare Pages dashboard → "Connect to Git" → pick repo
- Set production branch =
main - Every other branch auto-gets a preview URL on push
- Open PRs → CF posts preview links in comments
Best for: any site you'll touch more than once over weeks.
Pattern P3 · Personal hub (your loudog.uno vision)
- One Pages project for the apex domain
- Subdirectories per person:
/share/mom/,/share/james/ - JSON manifest defines people → projects mapping
- Workers Function does routing + per-user Access enforcement
- R2 bucket for media, D1 for metadata
Best for: a long-lived "infrastructure for sharing" that grows over years.
Pattern P4 · Media-heavy site (photo essays, videos)
- HTML on Pages (small, fast)
- Media on R2 (private bucket, signed URLs OR public bucket with Cache-Control)
- Worker handles URL signing for private content
- Use Cloudflare Stream for videos >25MB (~$5/1000 min stored)
Best for: the Mom site once you add the long videos, any portfolio with hi-res images.
Pattern P5 · Agentic burst-deploy (where we're heading)
- Broad CF API token in env
- Cloudflare MCP server registered with Claude
- Skill or prompt template that runs the pipeline end-to-end
- "Build me a site about X" → 5 minutes later, live URL
Best for: bespoke gifts, ad-hoc dashboards, micro-projects, party-trick demos.
8 · What "expert" looks like (cheatsheet)
The mental model of someone who can deploy any bespoke page in under 60 seconds:
Environment
# In ~/.zshrc: export CLOUDFLARE_API_TOKEN="..." # broad token, see Agentic Pipeline doc §5A export CF_ACCOUNT_ID="..." # your account id alias cfdeploy="npx wrangler pages deploy . --project-name=$(basename $(pwd))"
The starter template (keep this around)
# ~/templates/static-site/ . ├── index.html # your page ├── _headers # security headers ├── robots.txt # Disallow: / if private ├── functions/ │ └── _middleware.js # password gate, if needed └── README.md # notes for future-you
The five commands you actually need
## 1. New deploy npx wrangler pages project create $NAME --production-branch=main npx wrangler pages deploy . --project-name=$NAME --commit-dirty=true ## 2. Custom domain (once CF token has Zone:DNS:Edit) curl -X POST -H "Authorization: Bearer $CLOUDFLARE_API_TOKEN" \ https://api.cloudflare.com/client/v4/accounts/$CF_ACCOUNT_ID/pages/projects/$NAME/domains \ -d '{"name":"sub.loudog.uno"}' curl -X POST -H "Authorization: Bearer $CLOUDFLARE_API_TOKEN" \ https://api.cloudflare.com/client/v4/zones/$ZONE_ID/dns_records \ -d '{"type":"CNAME","name":"sub","content":"$NAME.pages.dev","proxied":true}' ## 3. Tail logs (debugging Functions) npx wrangler pages deployment tail --project-name=$NAME ## 4. Delete project (clean up throwaways) npx wrangler pages project delete $NAME ## 5. List everything npx wrangler pages project list
Decision tree
Need a public page now? → YES: Pages, no functions, deploy Need auth? → 1 password OK: add functions/_middleware.js cookie gate → real user identity: switch to Cloudflare Access (free < 50 users) Need dynamic logic (API, form, db read)? → Light: Pages Functions → Heavy: standalone Worker Need to store files > 1MB? → Public: R2 public bucket + Cache-Control → Private: R2 + signed URLs via Worker Need a custom domain? → On Cloudflare: register via Registrar, auto-bind via Pages domains API → Elsewhere: add CNAME at your DNS host pointing to project.pages.dev Going to maintain this for > 1 month? → Git-connect the project, never run wrangler again
9 · Common footguns & fixes
| Footgun | Symptom | Fix |
|---|---|---|
wrangler pages deploy scans CWD for functions/ |
Deploying project A inherits middleware from project B that you were working in earlier | Always deploy from a clean dir, or use --cwd /path/to/clean |
| Project must exist before first deploy | "Project not found" on first deploy |
Run wrangler pages project create <name> first |
OAuth token has zone:read only |
Can register custom domain on Pages, but DNS CNAME fails | Create API token with Zone DNS · Edit (see Agentic Pipeline doc §5A) |
| CF Pages caches aggressively | You re-deploy but old version still shows | Hard refresh (Cmd+Shift+R) or use the https://<hash>.<project>.pages.dev preview URL |
| Function unset-token requirement | "Authentication error" when deploying to Pages with CLOUDFLARE_API_TOKEN set |
Either: (a) (unset CLOUDFLARE_API_TOKEN; wrangler ...), or (b) make sure your token has Cloudflare Pages · Edit |
| HEIC images don't render in browsers | Photos show as broken icons | Filter to .jpeg derivatives only, or convert HEIC → JPEG at copy time |
| iCloud "Optimize Mac Storage" | Originals not on disk, only thumbnails | Use resources/derivatives/masters/ for ~1024px web-ready JPEGs |
| Pages Function template literals + apostrophes | Build error: "Expected ':' but found 't'" |
Use backticks throughout, or rephrase ("was not" instead of "wasn't") |
| 25 MB per-file limit | Single big asset (PDF, video) fails to upload | Move to R2 — embed via https://r2-bucket.r2.dev/... URL |
| Pages preview URLs aren't cached | Slow first load on dev iteration | Expected. Use the <project>.pages.dev (production alias) for testing if you need cache. |
What I'd do in your shoes
- Finish the CF API token setup (the screen you're on right now). That unblocks fully agentic operation.
- Pick a default workflow: for ad-hoc personal stuff = CLI imperative (Level 2). For loudog.uno hub = Git-connected (Level 3) + Terraform later (Level 4).
- Make a starter template at
~/templates/static-site/with the files listed in §8. Future deploys start ascp -r ~/templates/static-site/ ./my-thing. - Install the Cloudflare MCP server — see Agentic Pipeline doc §5B. After this, "deploy a private page about X" is one sentence to me.
- For the hub vision: Pages (HTML) + R2 (media) + Access (auth) + D1 (metadata) + Worker (routing). All under one domain, all free at personal scale.
Document built 2026-05-11 · for Lou DeSantis · by Sai (claude-opus-4-7).
Companion to Agentic Cloudflare Pipeline.
Pricing/limits are current as of 2026-01 and may change — verify on each vendor's pricing page before committing infrastructure decisions.