Migrating off Deno Deploy
Oct 11, 2026
This is the actual plan I worked from when the Deno news landed, kept honest including the parts that didn't work. It was written with an AI agent (ZCode / GLM), I made the calls, and it shipped the same day.
Context
On October 9, 2026 Deno announced the team is joining Cloudflare. Deno Deploy shuts down in roughly six months; the Deno runtime gets about a year of maintenance, then development ends.
This site was built Deno-first: Fresh 2 SSR, scheduled to deploy on Deno Deploy. The question was where to land instead.
The audit that shaped everything
Before choosing, we audited what the site actually depended on:
- The built server default-exports
{ fetch }— exactly the Cloudflare Workers module shape. - Application code contained exactly two Deno-specific API calls — both filesystem reads loading markdown content at boot.
- Everything else was framework internals.
Two calls away from portable. That set the plan.
Path one: the official recipe (abandoned)
Fresh documents a Cloudflare Workers deploy via @cloudflare/vite-plugin in the
Vite config. We tried it, in every plugin ordering. All of them fail inside
Fresh's own CSS sub-build, and Fresh's JSR page disclaims that Workers
compatibility is unvalidated.
We could have debugged the plugin interop — and still carried runtime Deno.*
risk on workerd. Instead we asked the better question.
Path two: does this site need a server at all?
The site is read-only. Twenty-odd routes, no forms, no sessions, no per-request data. Every page can be baked at build time:
- Bundle content — a generator inlines
content/markdown into the build, removing the last filesystem reads. - Prerender — a script boots the built Fresh server, renders every page
plus the sitemap and 404 into
dist-site/, mirrors the client assets, and verifies every referenced URL resolves to a real file. - A 15-line worker (web-standard APIs only) handles what static files
cannot: 301s for legacy
/posts/*URLs. Everything else defers to static assets.
What shipped
0514e87— content bundled at build time, no runtime FS readsdfb56f6— prerender pipeline, worker, wrangler configd4901f0— CI deploys to Cloudflare Workers with wrangler
Verified locally through real workerd before pushing: every page 200, branded 404 with a real 404 status, legacy URLs 301 correctly, islands and fonts loading.
What I'd do differently
Nothing structural. Two learnings worth keeping:
- The asset verifier in the prerender script paid for itself on day one — it caught a nav link to a route that didn't exist yet.
- Build hashes differ between local and CI environments; always verify against the URLs the live pages reference, never a stale local build.
Production now runs with zero server runtime. The Deno runtime's eventual end-of-life cannot touch the live site — Deno is only used to build, and the deployed artifact is plain files plus a 15-line worker.