/writing
Hybrid Next.js: Static Export and SSR from One Codebase
· 3 min read
A marketing site I worked on had to deploy two ways. One target wanted a fully static export: no servers, no API routes, just files. The other was Vercel, where preview deploys needed real server rendering for CMS draft mode and a few server-only handlers. Both had to come from the same codebase.
Next.js does not love this. API routes either exist or they do not, and the router resolves dynamic features at build time. Write something that depends on server rendering, then run a static export, and the build either fails or quietly produces something that breaks at runtime.
The mode switch
The fix was to treat the deploy target as data. An environment variable declared which mode the build was for, and the framework config read it. Routes that needed server rendering said so, and a static-export build left them out entirely. A Vercel build put them back. No second branch, no second config, one knob.
Most routes did not care either way. The few that did, preview mode and the draft handlers and anything calling a server-only function, declared it once at the top of the route and let the build sort it out.
The Suspense cleanup
Static export does not tolerate hooks that read URL state during prerender. Anything touching search params at render time had to sit inside a Suspense boundary, or the export would error or silently drop the page.
I fixed it per component. Each form or widget that read search params got its own boundary around just the part that needed it, with a sensible fallback. Hoisting one boundary into a shared layout was tempting, but keeping them scoped made each diff small and each intent obvious.
The rule it left behind
The switch outlived the migration that prompted it. Every route written afterwards declares what it needs and gets resolved by the build, which means nobody has had to think about the two targets since. That was the actual deliverable. Not the dual deploy, but the fact that a second deploy target stopped being a thing anyone designs around.
What I learned
When the same code has to ship to two targets that disagree about what happens at build time and what happens at request time, forking is rarely the answer. Give every route a way to declare what it needs and let the build resolve it. The switch covered every route I wrote afterwards, which was the part that mattered.
More writing
Designing URLs for a Printed QR Code
A QR code printed in a book can never be reprinted. How that constraint shaped number-keyed routes, an owned domain, and QR codes pinned to a hardcoded origin.
· 2 min read
What My Thesis Assumed About Hardware
Five years on from my paper about coordinating jobs across weak devices. The hardware assumption aged badly. The reliability assumption did not age at all.
· 3 min read
One Source of Truth for Fair-Usage Limits
A pricing policy is only as real as the number of systems that agree on it. Nine published usage bands, one table, and three codebases reading it.
· 4 min read