What deploying my garden taught me about routing
The garden is live. Getting it there taught me something I had not thought about while building it locally: a single-page app and its own build output can quietly compete for the same URLs, and the dev server is too forgiving to tell you.
The collision
The compiler wrote its JSON into public/garden/. The app reads notes at
/garden/<slug>. Locally, both worked, so I never questioned it. But a
static host resolves the filesystem before it applies a rewrite, and
/garden was a real directory with no index.html in it. So
the request matched something, found nothing to serve, and never fell through to the app. The
garden listing, the most-used page on the site, would have 404'd in production while working
perfectly on my machine.
I found it by refusing to trust the dev server: I served the built dist/ the way
the host would, filesystem first and rewrite second, and walked every route.
The fix was not a clever rewrite rule. It was removing the ambiguity, moving compiled
data to /garden-data/ so app routes and build output can never shadow each other.
What I'd tell myself before the next deploy
- Reserve a namespace for build output on day one; do not let it share a prefix with a route.
- Exercise the production bundle under production routing semantics before pushing, not after.
- A passing dev server is evidence about the dev server, and nothing else.
This note is also the first real test of the thing I built: it reached the live site by being a file in a folder, with no other change to the project. That was the whole promise of the compiler and of the garden itself. If publishing had needed anything more than a commit, the design would have been wrong.
The habit of checking the built artifact rather than the friendly local one is the same instinct that Spec-driven development with OpenSpec and AI agents enforces on the other end: decide what the system must do, then go and verify it actually does that, in the place it really runs.