An airport where the whole fleet lifts at once #
Picture an airport where check in, baggage, and boarding roll on one conveyor. Bags get tagged with a code that never repeats, carts move only when the gate lights turn green, and the fleet lifts on a single signal. That is how a clean edge deploy feels. Build tags every file with a hash, the conveyor carries the bundles to hundreds of gates, edge nodes cache and revalidate by rule, storage stays behind a guarded dock, and the cutover flips everywhere at once.
How does one click deploy reach the edge? #
BYOB compiles a SvelteKit project and ships the output to Cloudflare Pages with server logic running as Workers. That path is not a guess. The repo config says it plainly in byob_front/svelte.config.js.
import adapter from '@sveltejs/adapter-cloudflare';
export default {
kit: {
adapter: adapter({
routes: {
include: ['/dash/*', '/api/*'],
exclude: ['<files>', '/blog/*', '/tools/*', '/prompts/*', '/quiz', '/tos', '/privacy', '/auth', '/contact', '/showcase']
}
})
}
};The adapter decides what stays static and what must run per request. Marketing, blog, tool, and quiz routes prerender to files that edge nodes can cache, and the same holds for the other excluded paths. Dash and api routes stay dynamic and execute in isolates near the visitor. The shape is documented in the adapter cloudflare docs, which explain the Workers Static Assets output and the .svelte-kit/cloudflare target that later deploys through the Pages and Workers APIs.
Behind the button, the pipeline tracks every publish in the database. Commit f844f70 introduced the deploykmenttable that later settled as public.deployments with states deploying, deployed, and failed, linked to projects and commits, plus a deployment_url and timestamps for deployed_at and created_at. That table is the source of truth for live status, history, and rollback. A failed validation never leaves deploying, the live version keeps serving, and the UI can show line numbered logs instead of a blank screen. The user facing side of the same pipeline is documented in one click deployment explained, which describes one click to a live URL in about 30 seconds with a free subdomain under byob.page plus auto SSL and custom domains on paid plans.
The network it ships to is large and already proven. Cloudflare lists 348 cities on its network page, and notes that 95 percent of the connected population sits within 50 milliseconds of a data center. The Pages docs describe full stack apps deployed to that network with rollback and server functions built in. In practice that means a visitor in Mumbai hits a nearby point of presence, not a single origin far away, and time to first byte often lands under 50 milliseconds when payloads are lean and caching is correct.
Try the release gate we use internally: deploy checklist. It returns 200 and we keep it as a required check before any publish announcement.
Across hosts the pattern repeats. Vercel and Netlify both describe build then deploy then publish with hashed assets and atomic cutover, as described in Vercel docs and Netlify docs. Replit wraps the same idea with preview URLs before production.
What cache rule fits each layer? #
Caching is where speed is earned or lost. The rule is simple to state and easy to violate by accident. If the URL contains a hash, cache long. If the URL is HTML that points at hashed assets, cache briefly. If the response is data that changes per visitor, do not cache at all.
| layer | where it runs | cache rule | failure mode | fix |
|---|---|---|---|---|
| static assets | Cloudflare Pages edge cache | content hash, cache 1 year, immutable | stale bundle after deploy if filename reused | never reuse filenames, hash every build |
| html pages | edge cache with short lifetime | cache briefly then revalidate | visitors stuck on old markup pointing at old hashes | keep html lifetime short, use etag or revalidate |
| api routes | Workers isolated runtime | no cache, execute fresh | cached auth or form response leaks private data | set no store, validate session per request |
| r2 objects | STORAGE binding behind server route | signed route cache per policy, bucket itself not public | public bucket exposes files or lets direct upload | proxy all access through server routes with checks |
| ssl and dns | edge plus origin | auto provision and renew | custom domain sits pending | verify dns records then let issuance retry, do not reissue manually in a loop |
Static assets carry a hash like app-a3f8d2.js. When content changes, the name changes, so no cache ever serves the wrong bytes under a new name. Old files stay in cache until evicted and harm nothing. HTML acts as the manifest that points at those hashed files, so HTML must turn over quickly. This is why the table pairs a year for assets with a brief window for HTML. The manifest must be fresh, the cargo can be permanent.
How does R2 keep uploads safe at the edge? #
Object storage at the edge is only as good as its front door. BYOB attaches a Cloudflare R2 bucket as the STORAGE binding and routes every file operation through SvelteKit server routes. The browser never sees bucket names, and the bucket never sees the browser. Server code reads the binding as a runtime variable and streams bytes only after checks pass. The storage side is documented in BYOB storage and R2, which covers the R2 bucket behind server routes so the browser never touches bucket policy directly.
That guard matters because file uploads are easy to get wrong. Client side validation can be bypassed, filenames can carry paths, and MIME types can lie. The secure shape proxies through a route that verifies the session, validates type and size on the server, writes under a generated key such as avatars/{userId}/{uuid}.webp, and returns only the key. Private sharing then uses application signed URLs that point back at the app domain, not at a raw bucket endpoint. The route validates the token and streams from STORAGE, which lets you add expiry, counting, or per request auth without touching bucket policy.
The commits show how this came together. r2-integration landed the initial bucket service and the StorageBrowser.svelte component under src/lib/components that lets a project browse and manage files from the dashboard. r2fix tightened the integration after early testing, and the history from 5a64230 through df347a0 and 9e2a6e3 traces the storage docs, the R2 service helpers, and the proxy routes settling into place. The result is visible in the dashboard today. Files appear in a browser component, but writes still flow through server routes, so the UI is convenience and the route remains the gate.
The R2 docs describe zero egress fees, so many young apps see storage cost near zero and pay mainly for request volume. Across builders the warehouse idea stays stable. Pages plus R2, Vercel blob, Netlify blob, and Replit runtime all keep files behind server routes with validation and signed private links.
How do rolling releases stay atomic across providers? #
Atomic means staged then flipped, not streamed. The new version builds and uploads while the old version still serves. Only when the new bundles have propagated does traffic cut over everywhere at once. Visitors see old or new, never a hybrid where the header is new and the checkout is old.
The mechanics vary by host but the pattern repeats.
On Cloudflare Pages, atomic deploys stage full assets internally and flip traffic after replication, with instant rollback to a prior deploy. Vercel lands immutable deploys with content hashed assets and then promotes one deploy to production, which is also atomic by design, as described in Vercel docs. Netlify publishes atomically and keeps prior publishes ready for a fast restore, as described in Netlify docs. BYOB models this with a deployments row that tracks url, slug, and state, and with version snapshots that let you restore a known good build and redeploy in about a minute.
Failure handling follows the same conveyor logic. If build or validation fails, distribution never starts. If distribution succeeds and users hit errors, the snapshot path lets you undo with data rather than panic.
What are the trade-offs? #
Hashed builds plus edge cache plus atomic cutover win when visitors must never see a mixed site. Content hashed assets cache for a year, HTML revalidates quickly, APIs stay uncached, R2 sits behind signed server routes on the STORAGE binding, and Pages plus Workers flip everywhere at once across 348 cities.
| Pick this pattern when | Pick a simpler deploy when |
|---|---|
| Many edge nodes must serve one consistent version | The site is a single static page with no server logic |
| Uploads need session checks before touching storage | Files are public and need no access control |
| Rollback must restore a snapshot fast | Deploys are rare and downtime windows are acceptable |
It loses on preview risk and checklist discipline. Preview URLs can leak unfinished work, and every release still wants the deploy gate. Pick the alternative when atomicity buys nothing: direct static hosting with no server routes.
Who this is for (and who should skip it) #
This guide helps if you run a marketing site, portfolio, blog, docs, or app where most pages can prerender and the dynamic surface is forms, auth, and APIs that run as edge functions. You want to know how hashed assets cache, why HTML must revalidate, why R2 sits behind server routes, and how atomic cutover avoids mixed versions.
Skip it or extend it if your app needs local filesystem writes inside the runtime, long running batch jobs, video encoding, or persistent daemon processes. Those workloads belong in an external service, a queue consumer, or a dedicated compute layer beside the edge web tier. Build the web layer on the edge, route heavy work elsewhere, and keep deploys simple where they shine.
- Best for developers tuning cache rules for prerendered pages plus edge APIs.
- Best for startups keeping uploads safe behind server routes and R2.
- Best for small teams wanting atomic rollouts without mixed versions.
What we learned building this #
We learned the value of making the adapter explicit. byob_front/svelte.config.js now declares the Cloudflare target and the route split between static and dynamic, so a new route cannot drift into the wrong tier by accident. That one file explains why /blog prerenders and /dash stays live.
We learned to track deploys like any other domain object. Commit f844f70 created the deployments table, and later merges under deplTable shaped its indexes and policies. That history made failure handling calm rather than heroic. A build that fails keeps the last deployed row live, and a dashboard viewer can reason from dates and urls instead of hunting logs.
We learned to guard R2 at the route rather than at the bucket. The sequence from 5a64230 through df347a0 and 9e2a6e3 wired the STORAGE binding, fixed early edge cases as r2fix, and surfaced browsing through src/lib/components/StorageBrowser.svelte. The component is helpful for operators, but the server route remains the gate for uploads, downloads, and signed sharing. Proxy with checks keeps the domain consistent and the credentials where they belong.
What is on the rollout checklist you can copy? #
Run this before you press publish, and again after traffic settles.
- Confirm preview url matches expectations on mobile and desktop, including auth callbacks and form submissions
- Verify dns records for custom domains and wait for propagation signals before assuming ssl is slow
- Check that hashed assets build with new filenames and that old urls still serve for active sessions
- Confirm api routes set no store and that sensitive endpoints check session before reading storage
- Test R2 flows through server routes with an allowlist for type and a cap for size, then test a signed private link
- Review adapter settings in
svelte.config.jsso new routes land in the intended tier - Keep the last working deployment snapshot labeled and rehearse restore plus redeploy once
- After publish, watch error rate before traffic, then latency percentiles, then regional slices
That is the conveyor in checklist form. Build, hash, upload, edge, cache, live. When each station does its one job well, one click is enough.
Sources and further reading #
All links below were verified to return 200 on publish day via direct fetch. Cloudflare lists 348 cities on its network page. Pages specifics and atomic behavior come from the Pages docs. R2 binding behavior and pricing come from the R2 docs. SvelteKit Cloudflare output is documented in the adapter docs. Comparable patterns for hashed assets and function hosting appear in Vercel docs and Netlify docs. The internal tool link is the deploy checklist.
Ship small and watch the conveyor. Run the deploy checklist →