Plate № 04 · Deployment mechanics
A pattern from the gf.cx specimen book
BASE_URL · one-line swap
"Codified after the found.gf.cx → io.gf.cx/found migration required sed across four external files to clean up hardcoded preview URLs. The next subdomain starts correct rather than getting fixed at swap time."
The problem
A subdomain spins up on a Pages preview URL (foo-gf-cx.pages.dev) and its custom domain (foo.gf.cx) binds days later. In between, Worker code, tag-previews, OG images, analytics, README copy — every surface composing an absolute URL — references the wrong place. Hand-find-replace at swap time always misses one.
The shape
Export one canonical BASE_URL constant from one shared module. Every other surface imports it. Binding day = one-line edit + redeploy flips everything.
// src/config.ts
export const BASE_URL = "https://foo.gf.cx";
// everywhere else:
import { BASE_URL } from "./config";
const previewUrl = `${BASE_URL}/preview/${id}`;
When it breaks
- URLs in static HTML/CSS don't import — they need a build-time substitution (esbuild
define, jinja templating). Cover that surface separately or it'll be the one that slipped through. - Cross-repo references (subdomain A linking subdomain B) — each repo keeps its own
BASE_URL; cross-refs go through a redirect or a shared catalog, not a direct import across repos.
Reusable elements
The named, copyable pieces — lift any one without the others:
- The single
BASE_URLconstant — oneexport constinsrc/config.ts(or equivalent); zero other files hold an absolute origin string. The comment above it names the swap contract explicitly so future contributors don't bypass it. - The one-line swap site — changing
BASE_URLfrom the.pages.devpreview to the custom domain and redeploying is the entire migration. No search-replace, no grep audit, no missed file. - The preview-vs-prod convention — during development the value is the
.pages.devURL; after the custom domain binds in the CF dashboard it becomes the canonical subdomain. The constant is the only place that distinction lives.
Reference
- Source
- Dan, 2026-05-28 · codified from the
found.gf.cx→io.gf.cx/foundmigration, where retroactivesedacross four files was the painful path that motivated this pattern. - In use / example
- svc.gf.cx — the service-record routing Worker exports
BASE_URL = "https://svc.gf.cx"fromsrc/index.tswith an inline comment naming the one-line swap contract; every self-referencing absolute URL in the Worker imports from that single export. - Reusable elements
- Single
BASE_URLexport · one-line swap on bind day · preview-vs-prod constant convention (listed above). - Origin
found.gf.cx→io.gf.cx/foundsubdomain migration, 2026-05-28 — the four-filesedcleanup that made the pain concrete.- Build cost
- ~5 min to wire into a new scaffold; zero ongoing overhead. The return is felt at every domain-bind after that.