What changed, and why
Glassbox used to reach production through a container: CI built an nginx image, pushed it to ghcr.io, and a k8s cluster pulled it. This PR retires that whole path. Hosting moves to Vercel, whose git integration builds and serves the site straight from this repo. A push to main becomes the production deploy. A pull request gets a preview URL.
Nothing under src/ or tests/ changes. What leaves is plumbing: 90 lines of container machinery (Dockerfile 26, nginx.conf 45, .dockerignore 19) and CI's image job, 23 deleted lines of ci.yml. What arrives is smaller still, a 15-line vercel.json plus a README section. The part worth real review is the serving policy nginx used to enforce, mapped rule for rule in the next section.
The infra half of the move lives in the companion PR on the workspace repo, mseeks/zo#20: the DNS flip and the k8s stack retirement.
vercel.json: nginx's serving policy, distilled
The old nginx.conf did five things. Four of them either move here or become platform defaults; the fifth (/healthz for k8s probes) had no job left to do.
The whole file. Two rules: immutable hashed assets, SPA fallback.
vercel.json · 15 lines
| nginx rule | where it lives now |
|---|---|
location /assets/ → Cache-Control: public, max-age=31536000, immutable | the headers rule here, byte-identical value |
try_files $uri $uri/ /index.html (SPA fallback) | the rewrites rule here |
try_files $uri =404 inside /assets/ | the rewrite skips /assets, so a missing hashed asset stays a plain 404 |
gzip on + types | Vercel compresses at its edge (gzip and brotli), no config |
location = /index.html → no-cache | Vercel's static default already makes browsers revalidate HTML |
location = /healthz for liveness/readiness probes | dropped — no pod, no probes |
access_log off tuning has no job on a managed edge.)CI: two jobs, not three
CI drops to two jobs. The deleted third, image, ran only on main and only after verify and e2e both passed. It logged in to ghcr.io, built the Docker image, and pushed :latest plus a :<sha> tag. One thing consumed those images: the cluster Deployment, which pulled :latest on restart. The companion PR retires that Deployment, so the job would now build artifacts nobody fetches. verify and e2e themselves are untouched, same steps and same triggers, byte for byte.
The verify job, unchanged. Below it (folded): the equally unchanged Playwright job — now the end of the file.
.github/workflows/ci.yml · 48 lines
⋯ 12 lines hidden (lines 1–12)
⋯ 21 lines hidden (lines 28–48)
The README says where the site lives now
The README gains a Deploy section between CI and Layout. It answers what a contributor actually asks. Where does the site run? What happens on a push, or on a PR? What does vercel.json own? Project settings, meaning the framework preset and the custom domain, stay in the Vercel dashboard on purpose. This repo never carried cluster manifests either; hosting-account plumbing lives outside it, same as before.
The new Deploy section, between CI and Layout.
README.md · 139 lines
⋯ 77 lines hidden (lines 1–77)
⋯ 48 lines hidden (lines 92–139)
How this was verified
The local gate ran green on this branch. npm run ci chains ESLint, prettier --check, the Vitest suite, and a production build. That build also proves the premise of the cache rule, since the output lands in content-hashed files under dist/assets/. Playwright still runs in this PR's CI, as on every PR.
After cutover, four requests against https://glassbox.mseeks.me settle serving parity. A hashed asset should answer with the immutable cache header. / should come back revalidation-friendly. A nonsense path such as /no-such-page should serve the app shell with 200. And a made-up /assets/nope.js should stay a 404 rather than turn into HTML. Merging this PR alone changes nothing live; the ordering (Vercel project first, DNS flip second, namespace deletion last) is documented in mseeks/zo#20.