Gryffin Forge
Next.jsCloudflarePerformance

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:

  1. The admin area must be protected before any content is sent.
  2. Analytics and error reports need an endpoint to receive them.
  3. 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.