Migrate from Vercel to Cloudflare Workers via OpenNext#1
Conversation
Deploys the site as a Cloudflare Worker with static assets using @opennextjs/cloudflare, replacing the Vercel serverless runtime. - Add @opennextjs/cloudflare + wrangler; cf:build/preview/deploy scripts - wrangler.jsonc: Worker name techempower, nodejs_compat, KV binding NEXT_INC_CACHE_KV for ISR, workers_dev subdomain enabled - open-next.config.ts: Cloudflare KV incremental cache override - next.config.js: transpilePackages for all runtime deps so Next 16's Turbopack inlines them (Workers has no node_modules at runtime) - lib/notion.ts: lazy import preview-images so lqip-modern/sharp are only loaded when isPreviewImageSupportEnabled is true (sharp can't run on Workers) - site.config.ts: disable preview images on Workers (no sharp, no Redis) - pages/api/version.ts: drop child_process/os, read WORKERS_CI_COMMIT_SHA / CF_PAGES_COMMIT_SHA with Vercel fallback during transition - lib/config.ts: add CF_PAGES_URL fallback alongside VERCEL_URL Preview deploy verified green at techempower.jp5.workers.dev on all routes (homepage, guides, /resources, sitemap, feed, /api/version). Domain cutover (techempower.org) deferred to a follow-up. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Migrates the app’s hosting from Vercel to Cloudflare Workers using OpenNext, including Workers/KV configuration and runtime adjustments to keep ISR working.
Changes:
- Added Cloudflare Workers/OpenNext configuration (
wrangler.jsonc,open-next.config.ts) and newcf:*scripts/deps. - Updated runtime configuration/env handling for Cloudflare (commit metadata + API host resolution).
- Adjusted Next.js bundling and Notion integration to avoid Workers-incompatible preview image dependencies.
Reviewed changes
Copilot reviewed 7 out of 10 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
wrangler.jsonc |
Defines the Cloudflare Worker entrypoint, assets binding, observability, and KV namespace for ISR cache. |
open-next.config.ts |
Configures OpenNext to use a KV-backed incremental cache. |
package.json |
Adds OpenNext/Wrangler dev dependencies and Cloudflare build/preview/deploy scripts. |
next.config.js |
Expands transpilePackages to inline runtime deps for Workers (no node_modules at runtime). |
site.config.ts |
Disables preview image support (LQIP) in site config. |
lib/notion.ts |
Lazy-loads preview image module only when the feature flag is enabled. |
lib/config.ts |
Switches API host computation to prefer CF_PAGES_URL with Vercel fallback. |
pages/api/version.ts |
Removes Node-only runtime introspection; uses Cloudflare/Vercel env vars for commit info and reports platform. |
.gitignore |
Ignores OpenNext/Wrangler build artifacts and local dev vars. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| // LQIP preview images — disabled on Cloudflare Workers (requires `sharp` native binary) | ||
| isPreviewImageSupportEnabled: false, | ||
|
|
There was a problem hiding this comment.
isPreviewImageSupportEnabled is now hardcoded to false in the shared site.config.ts, which disables LQIP preview images for all deployments (local/dev, Vercel rollback, etc.), not just Cloudflare Workers. If the intent is "disable on Workers only", consider deriving this value from an environment/platform check (or a separate config override) so non-Workers environments can still enable preview images without editing source.
| function gitInfo() { | ||
| // Vercel provides these env vars at build time (frozen into the serverless bundle) | ||
| const sha = process.env.VERCEL_GIT_COMMIT_SHA; | ||
| const ref = process.env.VERCEL_GIT_COMMIT_REF; | ||
| const sha = | ||
| process.env.WORKERS_CI_COMMIT_SHA || | ||
| process.env.CF_PAGES_COMMIT_SHA || | ||
| process.env.VERCEL_GIT_COMMIT_SHA | ||
| const ref = | ||
| process.env.WORKERS_CI_COMMIT_REF || | ||
| process.env.CF_PAGES_BRANCH || | ||
| process.env.VERCEL_GIT_COMMIT_REF | ||
| if (sha) { | ||
| return { hash: sha.slice(0, 7), branch: ref || 'unknown', dirty: false }; | ||
| return { hash: sha.slice(0, 7), branch: ref || 'unknown', dirty: false } | ||
| } | ||
| // Fallback: try git (works locally, not on Vercel) | ||
| const info = { hash: 'dev', branch: 'unknown', dirty: false }; | ||
| try { | ||
| info.hash = execFileSync('git', ['rev-parse', '--short', 'HEAD'], { encoding: 'utf8' }).trim() || 'dev'; | ||
| info.branch = execFileSync('git', ['rev-parse', '--abbrev-ref', 'HEAD'], { encoding: 'utf8' }).trim() || 'unknown'; | ||
| try { execFileSync('git', ['diff', '--quiet']); } catch { info.dirty = true; } | ||
| } catch {} | ||
| return info; | ||
| return { hash: 'dev', branch: 'unknown', dirty: false } | ||
| } |
There was a problem hiding this comment.
gitInfo() no longer attempts a local git fallback, so /api/version will always report hash: "dev" when running locally even if a .git directory is present. If you still want accurate local version info without breaking Workers, consider adding an optional Node-only fallback using a dynamic import of child_process (and guard it behind detectPlatform() === "local" or a similar runtime check).
- Reformat pages/api/version.ts array literals per prettier - Use Number.parseInt (unicorn/prefer-number-properties) - Add .next/, .open-next/, .wrangler/ to eslint.config.js ignores so the OpenNext build output doesn't get linted locally Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Summary
Moves hosting off Vercel (free-tier warnings) and onto Cloudflare Workers with static assets via
@opennextjs/cloudflare. Same Next.js app, same Notion-as-CMS architecture, ISR preserved using a Workers KV namespace as the incremental cache backend.Preview deploy: https://techempower.jp5.workers.dev — fully green.
Key changes
@opennextjs/cloudflare+wrangleras devDeps;cf:build/cf:preview/cf:deployscriptswrangler.jsoncwithnodejs_compat, static assets binding, KV bindingNEXT_INC_CACHE_KVfor ISR,workers_dev: truenode_modulesat runtime — added all runtime deps totranspilePackagesinnext.config.jssharpis a native binary, can't run in Workers). Madelqip-modernimport lazy inlib/notion.tsso it's only loaded when the feature flag is on.pages/api/version.tsnow readsWORKERS_CI_COMMIT_SHA/CF_PAGES_COMMIT_SHA(withVERCEL_GIT_COMMIT_SHAfallback for rollback week). Same inlib/config.ts.Test plan
Smoke-tested on the preview URL — all routes return 200:
/homepage (16 KB, correct title + meta)/guides/how-to-use-techempower(383 KB, Notion blocks rendered)/guides/free-internet(684 KB)/resources(2.6 MB, heavy ISR page)/about,/donate/sitemap.xml,/feed/api/version(realm-sigil responds asforgerealm)/api/resources-more(2.4 MB)/api/search-notion— 500, but also broken on Vercel prod (Notion upstream 503). Pre-existing bug, not a regression./api/social-image— 500, also broken on Vercel prod. Pre-existing bug.Cold response times: 0.4s homepage / 1–2s guides / 2.8s resources. KV cache will cut repeat hits to <100ms.
Follow-ups (not in this PR)
techempower.orgas a Workers custom domain + DNS cutoverWORKERS_CI_COMMIT_SHApopulates and/api/versionreports the real commit hash.vercel/, removedeploy: vercel deployscript, stripVERCEL_*fallback code/api/search-notionand/api/social-image(both broken on Vercel today)🤖 Generated with Claude Code