Skip to Content
Getting StartedDeployment

Deployment

Deploy the template to Cloudflare Workers with OpenNext and the tracked custom-worker entrypoint. The developer preview  demonstrates a selected configuration of the commercial template. Your deployment needs its own Cloudflare resources and secrets; a successful deployment does not verify payment, email, OAuth or storage providers.

Select the application and target

Choose the application composition in src/config/product.ts before building. Free personal use needs no payment provider. The shipped declaration selects new personal purchases and organization enrollment; missing providers remain unavailable. For a fresh core-only application, choose productCompositions.core. Configure Docs, Blog, metering and personal API access independently.

Keep application scope separate from the release target:

SelectionMeaning
devLocal Worker on port 8787 with tracked wrangler.toml; build only
previewExplicit [env.preview] in the private profile; normally the Worker named <name>-preview
productionTop-level Worker and bindings in the private profile; writes require --confirm production
CandidateYour --candidate label linking Worker and D1 receipts to retained source and its lockfile

A domain used for a developer preview can belong to the top-level Worker. Select commands from its actual profile ownership, not from the word “preview” in the site’s description. APP_URL identifies the public origin; it does not select the Worker or database.

Prepare an isolated candidate

Use Node.js 24 LTS and npm 10.9.2, as in Installation. Prepare a separate complete source directory containing the intended changes, package-lock.json, content and migrations. Preserve that input so a candidate label can be traced to actual source. Include intended uncommitted/new files when releasing current work; a Git commit alone may omit them.

Exclude local databases, logs, generated output, caches and private environment/profile files from the source snapshot. In that directory, install physical dependencies:

npm ci

OpenNext writes into dependencies during bundling. Do not share or symlink node_modules from a running development checkout. Named local Next instances do not provide OpenNext installation isolation. After the clean installation, prepare only the selected target’s private configuration inside this candidate.

Prepare the private profile

cp wrangler.remote.example.toml wrangler.remote.toml

Replace the selected environment’s placeholders with your account, Worker name, route/custom domain and D1/KV/R2 bindings. Keep main = "custom-worker.ts" and the generated assets binding. Put top-level routes before TOML section headers; preview routes belong under [env.preview]. Tracked wrangler.toml stays template-safe.

Retain the example’s top-level keep_names = false. Wrangler’s name-preserving bundle transform can inject a helper into the theme initializer that is later serialized into the page, causing __name is not defined in the browser. Existing private profiles must also set this value before bundling and deployment.

For the selected D1 binding, set migrations_dir = "drizzle", relative to the private profile’s directory, and omit migrations_table or use "d1_migrations". The path must point to this candidate’s migrations. Preview bindings and variables must be declared for that environment; do not assume they inherit the top-level values.

The default profile is wrangler.remote.toml. All release commands support --config FILE, or WRANGLER_REMOTE_CONFIG_PATH; CLI overrides the environment variable. Use the same profile, target and candidate throughout the release.

Configure build inputs and runtime secrets

InputOwner
Public composition and brandingsrc/config/product.ts; rebuild after changes
Remote account, routes and resource identityPrivate Wrangler profile
Non-secret Worker settingsThe selected profile’s [vars] or [env.preview.vars], including the correct HTTPS APP_URL
Build-time Next.js environmentThe isolated candidate’s build environment; keep public values aligned with the target
Worker runtime secretsCloudflare secret storage; upload only the intended Worker keys
D1/seed helper credentials.env.cloudflare.preview or .env.cloudflare, according to target
Local-only values.env.local for local app; .dev.vars.dev for local Worker

Remote helper env files are not automatic Worker secret uploads. Runtime secrets do not supply Next.js public build-time values. Keep AUTH_SECRET stable for a continuing environment, use the target’s trusted origin, and supply the operator secrets required by the selected operations. Configure each provider only when it is intended to run. Development auto-login is unavailable in a production Worker.

Authenticate using Wrangler login or an appropriately scoped CLOUDFLARE_API_TOKEN. D1 release uses saved Wrangler login when no explicit token is configured. Its default helper env file is optional; an explicitly selected --env-file must exist. If helper inputs are needed, start from .env.cloudflare.example for the selected target:

npm exec -- wrangler whoami

For secret-only upload, create the private dotenv file consumed by the wrapper: .env.preview.secrets or .env.production.secrets. Include only Worker runtime secrets; Cloudflare API tokens and helper credentials do not belong in that upload. Keep these files out of source archives and version control.

# Preview target: reads .env.preview.secrets npm run secrets:upload:preview # Top-level target: reads .env.production.secrets npm run secrets:upload:production -- --confirm production

The target Worker must exist for secret management. For a new service, create it through the normal deployment flow, configure its secrets, then verify runtime readiness. Secret upload can change a running Worker independently of the source candidate. Use secrets:list:preview or secrets:list:production to inspect names without values. Keep real payment and outbound email configuration out of a preview unless you explicitly intend those effects.

Validate and release

Run from the prepared candidate. Replace release-001 with your chosen ID. Planning reads the profile and validates inputs but does not read credential files or contact Cloudflare:

npm run cf:deploy:preview -- --candidate release-001 --dry-run npm run db:release:d1:preview -- --candidate release-001 --dry-run

Run the local static and read-only configuration checks against the chosen inputs:

npm exec velite npm run check npm run adoption:check -- --lane preview

Adoption inspection checks the lane’s local input file, not the effective Worker environment or remote schema. Exit 0 can retain unverified checks. Reuse an applicable passed check when source and configuration have not changed.

For a preview target, release ordered D1 migrations before code that requires them, then build and deploy:

npm run db:release:d1:preview -- --candidate release-001 npm run cf:deploy:preview -- --candidate release-001

For the top-level target, use the matching production wrappers:

npm run db:release:d1 -- --candidate release-001 --confirm production npm run cf:deploy -- --candidate release-001 --confirm production

Use --skip-apply instead of a write when only verifying an already complete migration history. D1 release never seeds data. If initialization is needed, use the explicit remote initialization workflow after runtime configuration is ready.

cf:deploy* already builds the candidate before deployment. Use cf:build:preview -- --candidate release-001 or cf:build -- --candidate release-001 when you only need a build; deploying later runs a new build. A separate preliminary build is not required for every deployment.

Existing databases

Remote D1 release uses native ordered SQL migrations, including data migrations, and verifies history, indexes and foreign keys. It accepts an empty database or an exact continuous prefix of this repository’s d1_migrations history. It refuses existing tables with missing, foreign or gapped history.

For an unknown existing database, preserve the data and establish its migration lineage or a separately designed migration to a new database. Do not use schema push, reset, or invented “already applied” markers to bypass the refusal. An interrupted migration may have retained earlier successful files; inspect remote history before deciding the next action. There is no automatic D1 rollback.

Verify the deployed candidate

Record the actual Worker version returned by Cloudflare and the deployed HTTPS URL. Observe runtime health once, then run the public browser smoke against that explicit origin:

npm run health:example -- --origin https://your-preview.example.com PW_BASE_URL=https://your-preview.example.com npm run test:smoke:preview

The production smoke wrapper likewise requires PW_BASE_URL. These remote smoke commands reuse the deployed service and do not initialize or reset a database; install Playwright Chromium in the verification environment if it is absent.

The current public smoke covers home, login, pricing, Docs, installation and hidden contributor-guide routes. It expects Docs and new-purchase navigation selected with checkout unavailable. A core deployment intentionally returns 404 for pricing; use checks matching that composition instead of treating its expected absence as a deployment failure. Public smoke does not verify authenticated resources, administrator operations, payment confirmation, mail delivery or scheduled execution.

Health reports bounded runtime/database observations. A successful health response does not prove full schema history or business readiness. Verify any writable journeys using your own isolated test data and accounts, with external side effects configured deliberately.

Results, rollback and recovery

Build/deploy and D1 attempts write separate receipts under .cache/release-results/ in the candidate directory. Retain the source, profile identity, candidate ID, previous/current Worker version IDs and receipt locations in your release record. passed applies only to the named stage; skipped and unverified are not success. A lost response or retained running stage means remote effects may be unknown.

Before a release, inspect and retain the existing deployment:

npm exec -- wrangler deployments list --config wrangler.remote.toml --env preview

To restore a previously verified Worker version, first confirm it remains compatible with the current D1 schema and bindings, then use its explicit version ID:

npm exec -- wrangler rollback PREVIOUS_VERSION_ID --config wrangler.remote.toml --env preview --message "Restore the previous verified application"

For the top-level Worker, omit --env preview from both commands. Rollback changes the deployed Worker version; it does not undo D1 migrations, data writes or externally completed effects. Check secret and binding compatibility before rollback, then repeat health and affected browser checks. No database recovery or rollback rehearsal is implied by a successful deploy.

When moving a hostname to a separate Worker, preserve the original Worker, database and secrets. The custom domain and zone Worker route are independent mappings: a trigger command may move the custom domain while an existing hostname/* route still invokes the original Worker. Read back both mappings after any failure. A Worker version rollback does not restore hostname ownership.

The source includes node scripts/worker-domain.js --help for a bounded cutover or routing rollback. Keep the verified account, zone, domain and route IDs plus the two Worker names in the ignored root wrangler.routes.private.json. --config FILE --direction forward|rollback produces a read-only remote plan; add --execute only after reviewing it. The command checks current ownership, changes only the selected hostname’s two mappings, then reads them back. See scripts/.docs/operational-runbooks.md#worker-domain-cutover in the source for the configuration example and partial-failure recovery. Routing success still requires HTTPS and application checks; it is not a database recovery or a completed rollback rehearsal.

FailureNext action
Missing target/candidate or conflicting profile identityCorrect the command/profile and rerun --dry-run; the tool refuses before effects
OpenNext dependency isolation refusedInstall the lockfile into the candidate’s own physical node_modules; do not modify a shared installation
Cloudflare authentication or permission failureCheck the selected account and token scopes; do not print credentials
Custom domain moved but the old Worker still respondsInspect the separate zone route and both current owners before retrying or restoring routing
Unknown D1 history or interrupted applyPreserve data and inspect native history before any further writes
Worker deployed but login/health unavailableCheck runtime secret names, origin, selected bindings and migration result separately
Pages load but the browser reports __name is not definedSet top-level keep_names = false as in the remote example, then rebundle/deploy and verify initial theme and persisted theme selection
Smoke refuses to startSupply PW_BASE_URL and an installed browser; confirm the test’s expected composition
Interrupted local build left masked variable filesRun the restore command below in the original candidate directory
node scripts/run-opennext-command.js restore

This restores only that directory’s inactive, valid .dev.vars* stashes. Missing manifests, active owners and conflicting files are preserved for manual resolution. It does not restore dependencies, Worker versions, secrets or databases.

CI follows the same sequence: isolated locked install, private target inputs, applicable checks, ordered migrations, build/deploy, then explicit-origin verification. Keep receipts private where necessary; do not publish configuration values or provider error bodies.