Building strandgreengables.co.za with Next.js, OpenNext and Cloudflare
Strand Green Gables is a guesthouse we took over, and we've been rebuilding its marketing and digital side, old charm reaching new travellers. The website was a big part of that. It had to look the part, load quickly on a phone and not need a server to babysit, so I built it with Next.js, the OpenNext Cloudflare adapter and Wrangler, running on Cloudflare Workers.
It's mostly content: photos, room descriptions, a location section, some things to do and a blog with local guides. Bookings go through Airbnb, so there's no booking engine, no database and no accounts to build. That suits an edge hosted app well, and Next.js still gives me file based routing and good metadata and sitemap tooling for the SEO side.
Next.js is built around Node.js and Vercel, and Cloudflare Workers isn't Node.js, it's a V8 isolate runtime with its own APIs. OpenNext is the adapter that bridges the two. It takes the output of next build and repackages it as a Worker plus a folder of static assets, which is exactly what Wrangler knows how to deploy.
Step 1: Install the adapter and Wrangler as dev dependencies
pnpm add -D @opennextjs/cloudflare wrangler
Step 2: Add an open-next.config.ts, the defaults are enough for a content site
import { defineCloudflareConfig } from "@opennextjs/cloudflare"
export default defineCloudflareConfig()
Step 3: Describe the Worker in wrangler.jsonc
{
"$schema": "node_modules/wrangler/config-schema.json",
"name": "strandgreengables",
"main": ".open-next/worker.js",
"compatibility_date": "2026-06-12",
"compatibility_flags": ["nodejs_compat"],
"observability": { "enabled": true },
"assets": {
"directory": ".open-next/assets",
"binding": "ASSETS"
}
}
main is the Worker OpenNext generates during the build, so there's no need to write it by hand, and assets serves the static output (_next/static, images, fonts) straight from Cloudflare without invoking the Worker. nodejs_compat is needed because Next.js and some of its dependencies expect Node APIs. Leave it out and the Worker fails at runtime instead of at build time, which is a nasty surprise. observability turns on Workers logs. To attach the domain add a routes entry with "custom_domain": true and Cloudflare handles DNS and the certificate.
Step 4: Add the scripts to package.json
{
"scripts": {
"dev": "next dev",
"preview": "opennextjs-cloudflare build && opennextjs-cloudflare preview",
"deploy": "opennextjs-cloudflare build && opennextjs-cloudflare deploy"
}
}
next dev is for day to day work, preview builds the real Worker and runs it locally in the Workers runtime, and deploy builds and pushes to Cloudflare. There's also an upload variant that uploads a new version without promoting it, handy for checking something before it goes live.
Step 5: Add this to the bottom of next.config.mjs so next dev knows about Cloudflare bindings
import { initOpenNextCloudflareForDev } from "@opennextjs/cloudflare"
initOpenNextCloudflareForDev()
A few things to watch for. Run preview before deploy, because a build that works in next dev can still fail in the Workers runtime. The default Next.js image optimiser needs infrastructure behind it, so images: { unoptimized: true } with correctly sized WebP files is the simple route, and Cloudflare Images is there if it ever matters. Keep secrets in .dev.vars locally and set production ones with wrangler secret put. Workers have a size limit on the deployed script, so keep an eye on dependencies (server components help, a content site sends very little JavaScript anyway), and keep middleware light because it runs on every request.
For a content site like this it removes most of the moving parts. There's no server to patch, deploys are one command and rollbacks are a click in the Cloudflare dashboard. If this helps anyone deploying Next.js to Cloudflare, great, and the OpenNext Cloudflare docs and Wrangler docs are the place to go for the rest. If you're ever near Strand, the site is at strandgreengables.co.za.