Rebuilding intactic.net on the Cloudflare Edge
We migrated our own corporate platform from a split Supabase + Vercel stack to a fully serverless Cloudflare architecture — Workers for compute, D1 for content and admin data, R2 for media, and OpenNext for Next.js 16 — then instrumented everything with a repeatable performance harness.
Time to first byte measured against the production Worker from APAC edge colos
Cumulative layout shift recorded across all 8 tracked routes in the Lighthouse CI baseline
Compute, DNS, SSL, databases, and media all run on Cloudflare — no origin servers
The platform previously ran on two vendors: Supabase provided Postgres, auth, and row-level security, while Vercel hosted the Next.js app. That split meant two auth models to maintain, per-request subrequests to an external database, duplicated content logic between public pages and the CMS, and deployment coupling we did not own end to end. We wanted one serverless data plane, one deploy pipeline, and no external database dependency at all.
We ported the entire schema from Postgres to Cloudflare D1 (SQLite) following deliberate conventions — UUIDs as app-generated TEXT, timestamps as ISO-8601 TEXT, JSONB as JSON TEXT — and replaced row-level security with a dual-layer route-handler auth model backed by PBKDF2 password hashing and opaque session tokens in Web Crypto. The app ships through the OpenNext Cloudflare adapter onto a single Worker, with D1 databases for content and admin data, R2 for media and the incremental cache, a D1 tag cache, and a Durable Object queue for time-based revalidation. GitHub Actions applies idempotent migrations and seeds on every build and deploys to production on push to main.
- Next.js 16 App Router is served by a single OpenNext worker on Cloudflare Workers — there is no origin server to patch, scale, or pay for while idle.
- Two purpose-built D1 databases keep concerns isolated: intacticdb for public content and settings, intacticadmindb for admin users, PBKDF2 password hashes, and opaque session tokens.
- The OpenNext incremental cache layers R2 (page and data cache), a dedicated D1 tag cache for revalidateTag, and a Durable Object queue that drives time-based revalidation every 300 seconds.
- Media uploads land in an isolated R2 bucket (intacticcloud) and are served through /api/media, while fingerprinted build output ships immutable cache headers via a static-asset _headers policy.
- src/proxy.ts applies CSP, HSTS, and frame protections to every response, and blocks /admin on subdomains — the security boundary travels with the Worker.
- Full migration from Supabase + Vercel to Cloudflare Workers, D1, and R2
- OpenNext incremental cache: R2 bucket, D1 tag cache, and Durable Object revalidation queue
- Dual-layer admin CMS with PBKDF2 session auth on D1
- Automated CI/CD with idempotent migrations and seeds on GitHub Actions
- Performance measurement harness with Lighthouse CI baselines in docs/perf