# Cloudflare Workers + D1 Migration Guide Migrating the official Payload CMS [`with-cloudflare-d1`](https://github.com/payloadcms/payload/tree/main/templates/with-cloudflare-d1) template from OpenNext to vinext. Follow the [README](../README.md) first, then apply these changes to `payload.config.ts`. ### Step 1: Remove OpenNext imports ```diff - import { CloudflareContext, getCloudflareContext } from "@opennextjs/cloudflare"; - import { GetPlatformProxyOptions } from "wrangler"; ``` ### Step 2: Remove `fs`/`realpath`/`isCLI` These crash under Vite's module runner: ```diff - import fs from "node:fs"; - - function realpath(value: string) { - return fs.existsSync(value) ? fs.realpathSync(value) : undefined; - } - - const isCLI = process.argv.some((value) => - realpath(value)?.endsWith(path.join("payload", "bin.js")), - ); ``` Where `isCLI` is referenced elsewhere (e.g. `isCLI || !isProduction`), replace it with `!isProduction`. ### Step 3: Replace the cloudflare context helper Delete `getCloudflareContextFromWrangler` and the `cloudflare` assignment. Replace with: ```ts async function getCloudflareEnv(): Promise { try { const { env } = await import(/* @vite-ignore */ "cloudflare:workers"); return env; } catch { const { getPlatformProxy } = await import("wrangler"); const proxy = await getPlatformProxy( process.env.CLOUDFLARE_ENV ? { environment: process.env.CLOUDFLARE_ENV } : {}, ); return proxy.env as CloudflareEnv; } } const cfEnv = await getCloudflareEnv(); ``` `CloudflareEnv` is the interface generated by `wrangler types`. The `as CloudflareEnv` cast on the fallback is needed because wrangler's proxy returns an untyped env. ### Step 4: Update references ```diff - db: sqliteD1Adapter({ binding: cloudflare.env.D1 }), + db: sqliteD1Adapter({ binding: cfEnv.D1 }), - bucket: cloudflare.env.R2, + bucket: cfEnv.R2, ``` ### Step 5: Clean up wrangler.jsonc for vinext Remove the OpenNext-specific `main` and `assets` fields. The `@cloudflare/vite-plugin` manages these automatically based on the Vite build output: ```diff - "main": "node_modules/@opennextjs/cloudflare/dist/worker.js", - "assets": { - "directory": ".open-next/assets", - "binding": "ASSETS" - }, ``` Also remove `"remote": true` from D1 bindings — it requires Cloudflare auth for local dev: ```diff "d1_databases": [{ "binding": "D1", "database_id": "...", "database_name": "...", - "remote": true }] ``` ### Step 6: Avoid top-level binding assertions Cloudflare validates workers by executing module code during upload. Bindings (D1, R2) are **not** populated during this validation step. Any top-level assertions like `assert(cfEnv.D1, "D1 binding required")` will crash the upload. Access bindings at request time, or guard them: ```ts // ❌ Crashes during upload validation const cfEnv = await getCloudflareEnv(); assert(cfEnv.D1, "D1 binding required"); // ✅ Safe — D1 is only accessed when handling requests const cfEnv = await getCloudflareEnv(); ``` --- ## Reference - [payloadcms/payload#15876](https://github.com/payloadcms/payload/discussions/15876) — Vinext compatibility issues filed upstream - [payloadcms/payload#15761](https://github.com/payloadcms/payload/discussions/15761) — Community discussion on vinext support