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.