This site is a Next.js App Router project compiled to plain static files and served from Cloudflare Pages. Here's the full setup, since it's small enough to describe in one post.
Static export
Two lines in next.config.ts do most of the work:
const nextConfig: NextConfig = {
output: "export",
trailingSlash: true,
};
output: "export" turns next build into a static-site generator: every route is rendered to HTML at build time and written to ./out. There is no Node server in production — which also means no server-side surprises. trailingSlash keeps URLs stable (/blog/post/ maps to blog/post/index.html) so a static file host can serve them without redirect rules.
The catch: no SSR, no API routes at runtime, and every dynamic segment needs generateStaticParams(). For a blog, that's not a limitation — it's the correct shape.
Two workflows
Deployment is two small GitHub Actions workflows:
deploy.yml— on every push tomain, runnpm ci && npm run build, thenwrangler pages deploy ./out.preview.yml— on every pull request, deploy the same build to a preview branch of the Pages project and comment the preview URL on the PR.
Cloudflare Pages treats non-production branches as isolated preview environments for free, so every PR gets a shareable URL with zero extra infrastructure.
Why not a Worker?
The repo originally used the OpenNext adapter to run Next.js inside a Cloudflare Worker. That buys SSR — which a content site doesn't need — at the cost of a build transform, a runtime, and more moving parts in CI. Static files are the boring, durable option. Boring wins.