Cloudflare Deployment
Ozeaon is a Next.js App Router application deployed to Cloudflare Workers — a V8-isolate runtime, not Node.js. The @opennextjs/cloudflare adapter bridges the gap: it transforms the Next.js build output into a Worker entrypoint plus assets. Three artifacts define the deployment: open-next.config.ts (adapter options), wrangler.jsonc (resource topology per environment), and the package.json scripts that both developers and CI invoke.
Overview
Section titled “Overview”| Artifact | Role |
|---|---|
open-next.config.ts |
Selects optional cache, tag-cache and queue overrides — currently all commented out (defaults active) |
wrangler.jsonc |
Worker names, R2 bucket bindings and service bindings per environment |
package.json scripts |
The command contract: ci:build, ci:deploy, preview, typegen |
Deployment is CI-only. Merging a PR to main triggers production.yml, which calls _shared-deploy.yml. staging.yml is dispatch-only (the staging branch is retired). Never run pnpm ci:deploy locally. Rollback is git revert + push — the failing workflow writes rollback instructions into $GITHUB_STEP_SUMMARY.
Architecture
Section titled “Architecture”flowchart TD
subgraph sg_Config["Configuration"]
NextCfg["next.config.ts\ninitOpenNextCloudflareForDev()"]
OpenNextCfg["open-next.config.ts\ndefineCloudflareConfig()"]
WranglerCfg["wrangler.jsonc\nenv.staging / production / preview"]
end
subgraph sg_Build["Build"]
Adapter["opennextjs-cloudflare build"]
WorkerBundle[".open-next/worker.js"]
end
subgraph sg_Runtime["Cloudflare Workers"]
StagingWorker["staging-app-ozeaon"]
ProdWorker["production-app-ozeaon"]
PreviewWorker["pr-N-app-ozeaon"]
end
NextCfg --> Adapter
OpenNextCfg --> Adapter
Adapter --> WorkerBundle
WorkerBundle --> StagingWorker
WorkerBundle --> ProdWorker
WorkerBundle --> PreviewWorker
WranglerCfg --> StagingWorker
WranglerCfg --> ProdWorker
WranglerCfg --> PreviewWorker
next.config.ts calls initOpenNextCloudflareForDev() so that pnpm dev sees the same Cloudflare binding surface as the deployed Worker. The three named Workers map to env.staging, env.production and env.preview in wrangler.jsonc.
OpenNext Configuration
Section titled “OpenNext Configuration”open-next.config.ts calls defineCloudflareConfig() with every option commented out — the commented imports (r2IncrementalCache, d1NextTagCache, doQueue) serve as the upgrade path: uncomment the relevant line and provision the corresponding binding when the project needs cross-instance cache sharing, D1-backed tag invalidation, or a Durable Object revalidation queue. Running with defaults requires no D1 or dedicated cache bucket.
The Durable Object classes (DOQueueHandler, DOShardedTagCache, BucketCachePurge) are still exported by .open-next/worker.js even with all overrides disabled. This has a direct consequence for preview deployments — see CI/CD Workflows for the explanation.
Adopting Next.js cacheComponents is planned once the Cloudflare adapter stabilises; eslint.rules.cache.mjs currently bans use cache, cacheTag, cacheLife and updateTag.
Build & Deploy Scripts
Section titled “Build & Deploy Scripts”| Script | Purpose |
|---|---|
pnpm dev |
Local dev server with Cloudflare bindings via initOpenNextCloudflareForDev() |
pnpm preview |
Build Worker bundle + serve locally on port 3000 (safe to run locally) |
pnpm ci:build |
Build the deployable Worker bundle; type errors surface here |
pnpm ci:deploy |
Upload the Worker — CI-only |
pnpm typegen |
Generate cloudflare-env.d.ts (CloudflareEnv interface) from wrangler.jsonc |
pnpm clean-cache |
Remove .next, .open-next, .wrangler and cache directories |
typegen must run before ci:build. The CloudflareEnv interface it generates makes binding mismatches into compile-time errors; the canonical CI sequence is install → typegen → lint → ci:build. See package.json for the exact commands.
Deployment Topology
Section titled “Deployment Topology”wrangler.jsonc declares bindings per environment. Two bindings carry architectural weight:
R2_BUCKET— object storage. Staging and preview bindstaging-app-content; production bindsapp-content.WORKER_SELF_REFERENCE— a service binding pointing at the Worker itself, used forrevalidateTaginternal calls. It exists only insideenv.staging/env.production; the top-level config has noservicesblock deliberately.
Always pass --env staging or --env production when running wrangler deploy manually — omitting --env produces a Worker with no WORKER_SELF_REFERENCE, which fails silently at deploy time and only manifests when a route calls back into the Worker.
Secrets live in GitHub Environments (Settings → Environments), not the Cloudflare dashboard — the workflows inject them at build/deploy time. See Config & Constants for the full variable inventory.
Runtime Constraints
Section titled “Runtime Constraints”The V8 isolate runtime imposes hard rules on application code:
- No Node.js APIs (
fs,path,Buffer) at runtime; use Web-standard APIs (fetch,Request/Response,crypto). process.envis build-time only. Values are inlined byopennextjs-cloudflare build; a missing required variable is a build failure, not a runtime 500. All env access goes throughsrc/config/env.ts, which validates at build time.- No long-running background tasks. Work cannot outlive the request that spawned it.
Preview Deployments
Section titled “Preview Deployments”Every PR gets a separate Worker (pr-<N>-app-ozeaon) rather than a version of the staging Worker, because Cloudflare mints no preview URL for a Worker that exports a Durable Object — and the OpenNext bundle always exports three. Full lifecycle details — fixture strategy, migration drift guard, secrets isolation, operational gotchas — are in CI/CD Workflows: Preview Deployment Lifecycle.
Failure Modes & Edge Cases
Section titled “Failure Modes & Edge Cases”| Symptom | Cause | Resolution |
|---|---|---|
| Deployment fails | Build or upload error | Inspect $GITHUB_STEP_SUMMARY; fix forward with git revert + push |
revalidateTag fails |
Deployed without --env; no WORKER_SELF_REFERENCE binding |
Redeploy with --env staging / --env production |
| Build fails on env access | Missing/invalid variable in GitHub Environment | Fix the secret; validation is in src/config/env.ts |
| Stale build artifacts | Corrupted .next / .open-next state |
pnpm clean-cache && rm -rf node_modules pnpm-lock.yaml && pnpm install |
| Preview sends real email | PREVIEW_RESEND_API_KEY is a live key |
By design — treat any address in a preview as a real recipient |
Operational Notes
Section titled “Operational Notes”- CI pins tool versions independently of local
package.jsonranges; see theenv:blocks in_shared-build.ymland_shared-deploy.yml. - Three CI caches speed up
ci:build: the pnpm store (keyed onpnpm-lock.yaml), the Next.js build cache (.next/cache,.open-next/cache, keyed on lockfile + env + source hash), and Wrangler state (~/.wrangler, keyed on Wrangler version +wrangler.jsonc). Awrangler.jsoncchange invalidates the Wrangler cache. - The
changesgate (dorny/paths-filter) only triggers a build when a PR touchessrc/**,next.config.ts,open-next.config.ts,package.json,pnpm-lock.yaml,tsconfig.jsonorwrangler.jsonc. The deploy job is gated onclosed+merged == true— an unmerged close never deploys.
Extension Points
Section titled “Extension Points”| Extension | How |
|---|---|
| R2-backed incremental cache | Uncomment r2IncrementalCache in open-next.config.ts; provision the R2 binding |
| D1-backed tag cache | Uncomment d1NextTagCache; provision the D1 binding |
| Durable Object revalidation queue | Uncomment doQueue |
| New environment | Add env.<name> to wrangler.jsonc, re-run typegen, add GitHub Environment, extend _shared-deploy.yml |
| New binding | Declare in wrangler.jsonc → re-run typegen; mismatches become compile-time errors |
Related Links
Section titled “Related Links”docs/ops-deployment.md— authoritative operations document- CI/CD Workflows — preview lifecycle, Monday automation, full workflow graph
- Config & Constants — environment variable inventory
- SSR Rendering & Caching — how routes decide between dynamic and prerendered
open-next.config.ts— adapter configuration switchboardwrangler.jsonc— Cloudflare resource topologypackage.json— script contract