Shipping a static Next.js site on Cloudflare Workers
Why this site is a fully static Next.js export served by a tiny Worker: fast pages, near-zero cost, server-side admin protection and security headers.
3 min readGryffin Forge
This website is built with Next.js and React, but there is no Node.js server running anywhere. Every public page is pre-rendered to static HTML at build time and served from Cloudflare's edge. A small Worker handles the handful of things that genuinely need a server. Here is why, and how the pieces fit.
Static first
Almost everything on a portfolio and demo site is stable content: demo descriptions, articles, case studies, tool pages. None of it changes per visitor, so rendering it on every request would add latency and cost for no benefit.
With output: "export", Next.js renders every route — including dynamic ones like /demos/[slug], enumerated with generateStaticParams — to plain HTML files. The result:
- Fast first paint — HTML arrives complete, with no server rendering time.
- Minimal JavaScript — server components ship no client code; only interactive pieces (filters, calculators, the demo viewer) hydrate.
- Near-zero running cost — static assets on Cloudflare are served from cache.
- Simple failure modes — there is no application server to crash.
Where a server is still needed
Three things cannot be static:
- The admin area must be protected before any content is sent.
- Analytics and error reports need an endpoint to receive them.
- Commercial capabilities must be refused server-side, not just hidden.
A Worker sits in front of the static assets for /admin and /api/* only. It checks a signed session cookie for admin routes, rate-limits login attempts, records page-view counts in a Durable Object without cookies or IP addresses, and returns 403 for any commercial endpoint unless an explicit server-side flag is set. Every other request goes straight to the static files.
Draft content never ships
Content entries carry a status: draft, preview, published or archived. The build only generates pages for published and preview entries; preview pages carry noindex and are left out of listings and the sitemap. Drafts never become files at all, so there is nothing to leak.
Security headers from a file
Static assets on Workers support a _headers file, so security headers are declared once: a strict content security policy that only allows the demo renderer's origin in frames, X-Content-Type-Options, a conservative referrer policy and a permissions policy that disables sensors the site never uses.
Proper 404s
The static asset configuration serves the generated 404.html with a real 404 status code for unknown paths, so search engines never index soft-404 pages.
Trade-offs
- No per-request personalisation. Anything dynamic must run in the browser or the Worker.
- Rebuild to publish. Content changes require a build and deploy. For a site that changes a few times a week, that is a feature: every change is reviewed and versioned.
- No built-in image optimisation. Screenshots are converted to WebP at capture time and served at fixed sizes.
For a showcase site, those trade-offs are easily worth it: the result is fast, cheap, secure by default and easy to reason about.