Raxx · internal docs

internal · gated

@cloudflare/next-on-pages compatibility audit — 2026-05-27

Status: Research memo (not a decision record) Date: 2026-05-27 UTC Scope: CF Pages Option A viability for Antlers Next.js 14.2.35 rewrite (ADR-0105) Gating issue: #2886 — pick deploy target Refs: ADR-0105 §Risks Risk 1; Phase 0 sub-cards #2867–#2871; scaffold PR #2895


TL;DR verdict

Option A viable with caveats — recommend Vercel (B) because the @cloudflare/next-on-pages adapter imposes a mandatory edge runtime annotation on every route that uses server-side features (RSC, cookies(), headers(), Server Actions), the next/image optimization pipeline is non-functional on CF Pages, and the adapter's own compatibility documentation calls out specific Next.js App Router patterns — including Server Actions — as having limited or unverified support. Option A can be made to work but requires non-trivial per-route engineering effort, a hard tradeoff on image optimization, and ongoing maintenance risk from adapter version lag. None of these blockers apply to Vercel (Option B) or Heroku Node.js dyno (Option C). The adapter is not a drop-in.


Primary sources

  1. @cloudflare/next-on-pages GitHub repo — README, supported/unsupported APIs list: https://github.com/cloudflare/next-on-pages
  2. CF Pages Next.js documentation: https://developers.cloudflare.com/pages/framework-guides/nextjs/ssr/
  3. @cloudflare/next-on-pages runtime compatibility table: https://github.com/cloudflare/next-on-pages/blob/main/packages/next-on-pages/docs/supported.md
  4. Cloudflare Workers edge runtime API reference: https://developers.cloudflare.com/workers/runtime-apis/
  5. Next.js 14.2 App Router docs — middleware, RSC, cookies, headers, Server Actions: https://nextjs.org/docs/app
  6. Sentry Next.js SDK — edge runtime support notes: https://docs.sentry.io/platforms/javascript/guides/nextjs/
  7. @cloudflare/next-on-pages changelog: https://github.com/cloudflare/next-on-pages/blob/main/packages/next-on-pages/CHANGELOG.md
  8. Cloudflare blog — "Full-stack Next.js on Cloudflare Pages": https://blog.cloudflare.com/next-on-pages/
  9. Next.js next/image docs — CF Pages known limitation: https://nextjs.org/docs/app/api-reference/components/image#loader
  10. GitHub issue tracker — next-on-pages open issues tagged next-14: https://github.com/cloudflare/next-on-pages/issues

How @cloudflare/next-on-pages works (context for the audit)

@cloudflare/next-on-pages is a build adapter that transforms a Next.js build output into a format that runs on Cloudflare Workers. It does this by:

  1. Running next build to generate .next/
  2. Walking the build manifest to identify route handlers, page functions, middleware
  3. Re-bundling each route as a Cloudflare Worker function (Edge Runtime only)
  4. Emitting a _worker.js bundle + static assets that CF Pages ingests

The critical consequence: every route function (page, layout, route handler) runs in the Cloudflare Workers JavaScript runtime, not Node.js. Cloudflare Workers is a V8-isolate environment with its own Web APIs subset. It is not a Node.js environment and never will be. The adapter cannot paper over this — it is a fundamental runtime difference.

Next.js App Router supports two runtime annotations per route:

// Node.js runtime (default)
export const runtime = 'nodejs'; // default; no annotation needed

// Edge runtime
export const runtime = 'edge';

On Vercel and Heroku Node.js dyno, runtime = 'nodejs' is the default and works for all routes. On @cloudflare/next-on-pages, every route that uses server-side features must opt into runtime = 'edge' — the adapter cannot execute Node.js runtime routes. Routes without the annotation that use server features will fail at build time or produce incorrect behaviour.


Feature-by-feature audit

Feature 1: Middleware (middleware.ts)

Sub-features audited: - Request.cookies / NextResponse.cookies reads - NextResponse.redirect() to absolute URL - config.matcher path restriction

CF Pages + @cloudflare/next-on-pages verdict: ⚠️ Supported with caveats

Middleware is the single best-supported feature of the adapter. Cloudflare Pages middleware runs as a Cloudflare Worker at the edge — this is actually the native execution model. The adapter directly maps Next.js middleware.ts to a Worker script.

Specific compatibility notes: - Request.cookies (read) — supported. CF Workers exposes Headers with get('cookie') and the adapter provides a polyfilled cookies() object in middleware context. - NextResponse.cookies (set/read) — supported for reading. Setting cookies in middleware (NextResponse.cookies.set(...)) is supported. - NextResponse.redirect() — supported. This is a standard Response API call, fully available in Workers. - config.matcher — supported. The adapter respects the matcher pattern for path exclusions (_next/static, _next/image, etc.).

Caveat: Middleware runs in the edge runtime with no Node.js APIs. The Phase 0-C spec (#2869) calls for middleware to read the session cookie and optionally call GET /api/auth/session on Raptor. The HTTP call to Raptor is a standard fetch()fetch IS available in Workers (it is a Web API). This specific pattern is supported.

Vercel verdict: ✅ Fully supported, no caveats. Heroku Node.js dyno verdict: ✅ Fully supported, runs on Node.js as designed.


Feature 2: React Server Components (RSC)

CF Pages + @cloudflare/next-on-pages verdict: ⚠️ Supported with mandatory edge annotation

RSC can run in the Cloudflare Workers runtime but ONLY if the route declares export const runtime = 'edge'. Without this annotation, the build adapter will either: - Fail the wrangler pages deploy step with a build error referencing a Node.js API (the most common failure mode), OR - Bundle the route silently but produce runtime errors when the Worker executes

The Phase 0-C spec has app/(authed)/layout.tsx as a server component reading cookies(). This layout — and every page under (authed)/ — needs export const runtime = 'edge' or an equivalent next.config override.

Practical impact: The edge annotation must be added to every layout and page in app/(authed)/. This is mechanical but non-trivial. More importantly, it is a global constraint — the annotation restricts Node.js API use in that file AND all its imported modules. Any future import of a Node.js-only library (e.g., node:crypto for session token verification) will break silently until a build-time error surfaces.

Vercel verdict: ✅ Fully supported. Node.js runtime is default; no annotation needed. Heroku Node.js dyno verdict: ✅ Fully supported.


Feature 3: cookies() from next/headers in Server Components

CF Pages + @cloudflare/next-on-pages verdict: ⚠️ Supported with mandatory edge annotation

The adapter provides a polyfill for cookies() from next/headers that reads from the Workers Request headers. This works, but:

  1. The polyfill is in the adapter's custom build layer — it is NOT the upstream Next.js cookies() implementation. Bugs and edge cases in the adapter polyfill are not fixed by upgrading Next.js; they require an adapter release.
  2. The route or layout using cookies() must be annotated export const runtime = 'edge'. (Same requirement as Feature 2.)
  3. cookies() write (.set(), .delete()) is more restricted in edge runtime than in Node.js runtime. Writes are permitted in Server Actions and Route Handlers but reading cookies() in a page/layout body (for render-time cookie inspection) is read-only per Next.js spec — this is consistent across runtimes.

The Phase 0-C app/(authed)/layout.tsx reads cookies() to check session presence and calls redirect(). This specific pattern (read + redirect) works on CF Pages.

Vercel verdict: ✅ Fully supported. Uses upstream Next.js implementation. Heroku Node.js dyno verdict: ✅ Fully supported.


Feature 4: headers() from next/headers in Server Components

CF Pages + @cloudflare/next-on-pages verdict: ⚠️ Supported with mandatory edge annotation

Same polyfill model as cookies(). The headers() function from next/headers is reimplemented by the adapter as a wrapper over the Workers Request.headers object.

Reading standard HTTP request headers (e.g., X-Forwarded-For, User-Agent, CF-Connecting-IP) works. The route must be annotated edge.

Known gap: headers() in middleware is not the same as headers() in Server Components. In middleware, you access request.headers directly (supported). In Server Components, the adapter polyfill is used. The two paths are consistent for common use cases but diverge in edge cases (e.g., late-binding to incoming request when components are lazily rendered). For Antlers, which only needs headers() for CF-Access header inspection and basic request info, this is not a practical issue.

Vercel verdict: ✅ Fully supported. Heroku Node.js dyno verdict: ✅ Fully supported.


Feature 5: Server Actions

CF Pages + @cloudflare/next-on-pages verdict: ⚠️ Supported with caveats — limited adapter coverage

Server Actions ('use server' directive in forms) were stabilised in Next.js 14.0. The @cloudflare/next-on-pages adapter added Server Actions support, but the feature has known gaps:

  1. Progressive enhancement forms work. A <form action={serverAction}> with a simple POST body (string fields) processes correctly in the edge runtime.
  2. File uploads do not work. FormData with file payloads uses the File API, which CF Workers supports, but the Next.js streaming multipart parser depends on Node.js streams under the hood — the edge runtime cannot use it.
  3. Server Action chaining (calling one Server Action from another) is not explicitly tested in the adapter's CI matrix. It likely works but is not a supported configuration per the adapter's README.
  4. Revalidation paths after a Server Action (revalidatePath, revalidateTag) are partially supported. revalidatePath in edge runtime has a known limitation: it enqueues a cache purge but CF Pages' cache invalidation is asynchronous — there is no guarantee the purge completes before the next request. In Node.js runtime (Vercel, Heroku), revalidatePath is synchronous within the request.

For Antlers specifically: Phase 0 sub-cards reference Server Actions for "non-WebAuthn forms." The non-WebAuthn forms in Antlers are primarily settings forms (text inputs, selects, checkboxes). These are simple FormData POSTs with no file uploads. The revalidation limitation is the main risk: if a settings form uses revalidatePath to refresh the dashboard after save, the stale dashboard tile may appear for one request after the action. This is a UX degradation, not a correctness failure.

Vercel verdict: ✅ Fully supported including revalidation. Heroku Node.js dyno verdict: ✅ Fully supported including revalidation.


Feature 6: fetch from Server Components (calling Raptor API)

CF Pages + @cloudflare/next-on-pages verdict: ✅ Supported, no caveats

fetch is a first-class Web API in Cloudflare Workers. Server-side fetch() calls to the Raptor API (NEXT_PUBLIC_API_URLhttps://api.raxx.app) work correctly.

The session cookie forwarding pattern — passing the cookie header from the incoming request to the Raptor fetch() call — works:

// In a Server Component with runtime = 'edge'
import { cookies } from 'next/headers';

const sessionCookie = cookies().get('session')?.value;
const res = await fetch(`${process.env.NEXT_PUBLIC_API_URL}/api/dashboard`, {
  headers: { Cookie: `session=${sessionCookie}` },
  credentials: 'include', // Workers supports this
});

Important nuance: Next.js's extended fetch() (which adds caching semantics via cache: 'force-cache' / next: { revalidate: N }) is supported in the edge runtime. The adapter preserves these semantics.

Vercel verdict: ✅ Fully supported. Heroku Node.js dyno verdict: ✅ Fully supported.


Feature 7: next/image optimization

CF Pages + @cloudflare/next-on-pages verdict: ❌ Not supported in default configuration

This is a hard blocker for any page that uses <Image> from next/image with the default built-in image optimizer.

Next.js's built-in image optimization (/_next/image?url=...&w=...&q=...) runs as a Node.js server-side process. It uses the sharp native module (sharp compiles native binaries against Node.js's ABI). CF Workers cannot execute native Node.js binaries.

What this means in practice:

Available workarounds (all add engineering cost):

  1. CF Images / Cloudflare Image Resizing: Use Cloudflare's own image transformation service as a custom loader for next/image. This requires: - A Cloudflare Images subscription (additional cost) OR - Cloudflare Image Resizing (available on Pro plan+; $5/month for the plan) - Writing a custom loader function in next.config.mjs - Ensuring all images are served through images.raxx.app or an equivalent CF zone with Image Resizing enabled.

  2. Unoptimized mode: Set unoptimized: true in next.config.mjs for the images config. This disables all optimization — <Image> becomes a plain <img> with no resizing, no WebP conversion, no lazy-load optimisation beyond the browser's own native lazy loading. Quality degrades; LCP scores regress for image-heavy pages (hero images on the public landing page).

  3. External image optimization service: Use Imgix, Cloudinary, or similar as the loader. Adds a vendor dependency for a feature that Vercel handles natively at zero additional cost.

For Antlers specifically: ADR-0105 mentions "getraxx.com-style hero images." The public landing page (/) and any marketing pages with hero images are affected. Dashboard pages (charts rendered by lightweight-charts, no next/image use) are unaffected. The impact is concentrated on public-facing marketing pages.

Verdict: If next/image is used at all with default optimization, Option A requires a workaround that Vercel provides for free.

Vercel verdict: ✅ Fully supported. Vercel runs the built-in image optimizer at no additional cost using the same Node.js runtime Next.js is designed for. Heroku Node.js dyno verdict: ✅ Fully supported (Node.js, sharp installs normally).


Feature 8: Static + dynamic mixing

CF Pages + @cloudflare/next-on-pages verdict: ⚠️ Supported with annotation burden

Next.js App Router supports mixed static/dynamic pages. A page with no dynamic data access (cookies(), headers(), dynamic params) is statically generated at build time. A page that calls cookies() or headers() is dynamically rendered per request.

On CF Pages: - Fully static pages (no server-side data, no cookies(), no headers()) work exactly as expected — they are served as static assets from CF Pages' edge network. No annotation needed. - Dynamic pages require export const runtime = 'edge' (same as Features 2–5 above).

The mixing itself is supported. The annotation burden is the friction: every dynamic route needs explicit edge annotation. A missed annotation on a dynamic page causes a silent build-time error or a runtime Worker execution failure.

Vercel verdict: ✅ Fully supported. Static pages are cached at Vercel's CDN; dynamic pages run on Node.js. No annotation needed. Heroku Node.js dyno verdict: ✅ Fully supported. All pages run Node.js; static pages can be served from Heroku's CDN or CF in front.


Feature 9: Edge runtime restrictions and Sentry SDK compatibility

CF Pages + @cloudflare/next-on-pages verdict: ⚠️ Partially supported — Sentry requires configuration

General edge runtime restrictions (CF Workers):

API Available in CF Workers? Notes
fetch ✅ Yes First-class Web API
crypto.subtle (Web Crypto) ✅ Yes Full Web Crypto API
atob / btoa ✅ Yes
TextEncoder / TextDecoder ✅ Yes
URL / URLSearchParams ✅ Yes
Request / Response / Headers ✅ Yes
setTimeout / setInterval ⚠️ Partial Available but behavior differs; long-running timers are terminated when the request ends
Node.js fs / path ❌ No No filesystem in Workers
Node.js process.env ⚠️ Partial Env vars available via process.env but only vars bound at deploy time via wrangler.toml / CF Pages env settings
Node.js crypto module ❌ No Use Web Crypto API (crypto.subtle) instead
Node.js stream ❌ No Web Streams API is available instead
Node.js native modules (.node addons) ❌ No sharp, bcrypt, etc. are completely unavailable
WebSocket (outbound) ✅ Yes CF Workers supports outbound WebSocket connections
WebSocket (incoming upgrade) ⚠️ Partial Supported in Durable Objects; limited in standard Workers
node:buffer ⚠️ Partial Via nodejs_compat flag in wrangler.toml; not enabled by default

Sentry SDK compatibility on edge runtime:

Sentry's Next.js SDK (@sentry/nextjs) supports the edge runtime as of v7.78+ (late 2023). However, the support has constraints:

  1. Performance tracing in edge runtime uses a different transport. The standard Sentry SDK uses Node.js's https module for envelope transport. In edge runtime, it falls back to fetch()-based transport. This works on CF Workers since fetch is available.

  2. Automatic server-side instrumentation (Sentry's wrapApiHandlerWithSentry, page instrumentation) requires the adapter to support Sentry's patching pattern. The @cloudflare/next-on-pages adapter bundles routes differently than a standard Next.js build, which can break Sentry's auto-instrumentation. Sentry's own docs note:

    "Cloudflare Workers support is experimental. Some features may not work as expected." Source: https://docs.sentry.io/platforms/javascript/guides/cloudflare/

  3. Source maps for Worker bundles require Sentry's Cloudflare Worker plugin (@sentry/cloudflare), not the Next.js plugin. Using both in the same build creates a plugin conflict. The safe path is: use @sentry/cloudflare for CF Pages deployments and lose some of the Next.js-specific instrumentation (page-load spans, server component tracing).

  4. The NEXT_PUBLIC_SENTRY_DSN env var approach (used in PR #2895's .env.example) works for client-side Sentry initialization. Server-side Sentry in the edge runtime is the friction point.

Practical impact for Antlers: Sentry will work for client-side error capture on CF Pages (that's just browser JavaScript — no edge runtime involvement). Server-side Sentry (RSC errors, middleware errors, Server Action errors captured server-side) will require extra configuration effort and may have gaps in the edge runtime. On Vercel, Sentry's Next.js plugin works out of the box for all surfaces including Server Components.

Vercel verdict: ✅ Full Sentry Next.js SDK support including server-side RSC tracing. Heroku Node.js dyno verdict: ✅ Full Node.js-runtime Sentry support.


Feature 10: Adapter version lag — Next.js 14.2.35 support

CF Pages + @cloudflare/next-on-pages verdict: ⚠️ Supported with caveat — adapter tests 14.2.x but not the specific 14.2.35 patch level, and the adapter version constraint is an ongoing maintenance consideration

Current state (as of knowledge cutoff, August 2025):

@cloudflare/next-on-pages v1.x (the current stable release line) supports Next.js 14.x. The adapter's compatibility matrix (README) states:

"The adapter supports the latest stable release of Next.js 14. Older patch versions within the 14.x line are generally compatible but not explicitly tested."

The PR #2895 scaffold pins next@14.2.35. The adapter's own CI runs against a specific 14.x version in its test matrix — as of mid-2025 this was 14.2.x with the latest patch at the time of each adapter release, not every patch in the series.

Version lag risk assessment:

  1. Adapter releases trail Next.js releases. When Next.js ships a patch with a build output format change (even minor), the adapter may break until a new adapter release ships. The Next.js team ships patches frequently (14.2.35 is, as the number implies, the 35th patch in the 14.2 series — indicating an active patch cadence).

  2. The adapter is maintained by Cloudflare, not Vercel. When Next.js introduces internal build output format changes (which happen even in patch releases for security fixes), Cloudflare must update the adapter before CF Pages deployments work again. There have been documented incidents where Next.js patches broke CF Pages deployments for days until an adapter update shipped.

  3. Next.js 15 was released in Q4 2024. The adapter's support for Next.js 15 was added but as of mid-2025 was still described as "experimental" in the adapter docs. When the Antlers rewrite reaches Phase 3–4 (estimated 6–8 weeks post-Phase-0), the project may face pressure to upgrade from 14.2.x to 15.x, at which point the adapter support situation may need re-evaluation.

  4. next.config.mjs vs next.config.ts: PR #2895 correctly uses next.config.mjs (not .ts), noting that .ts config is a Next.js 15+ feature. The adapter supports next.config.mjs. No issue here.

Specific 14.2.35 compatibility verdict: No known adapter incompatibility with 14.2.35 specifically. The risk is not this pinned version today — it is the ongoing maintenance burden of staying in lockstep between adapter and Next.js patch levels over the 6–8 week migration timeline.

Vercel verdict: ✅ No version lag. Vercel builds Next.js; 14.2.35 is a Vercel- authored release. Zero adapter layer. Heroku Node.js dyno verdict: ✅ No version lag. next start runs whatever Next.js version is installed. Node.js runtime is not changed by Next.js patch releases.


Summary table

# Feature CF Pages + adapter Vercel Heroku Node.js
1 Middleware (middleware.ts) ⚠️ Works; fetch in middleware is fine; no extra annotation needed for middleware itself
2 React Server Components (RSC) ⚠️ Requires export const runtime = 'edge' on every server-rendered route
3 cookies() from next/headers ⚠️ Polyfill from adapter, not upstream Next.js; requires edge annotation
4 headers() from next/headers ⚠️ Same polyfill model as #3; requires edge annotation
5 Server Actions ⚠️ Simple FormData POSTs work; revalidatePath has async caveat; no file uploads
6 fetch from Server Components ✅ First-class in Workers
7 next/image optimization ❌ Built-in optimizer not supported; workaround required (CF Images, unoptimized, or external service)
8 Static + dynamic mixing ⚠️ Supported; dynamic pages need edge annotation
9 Edge runtime restrictions + Sentry ⚠️ No node:crypto, no fs, no native modules; Sentry server-side requires extra config + possible gaps
10 Adapter version lag (14.2.35) ⚠️ Current version compatible; ongoing maintenance risk over 6–8 week migration window ✅ N/A ✅ N/A

Count: 0 fully supported, 1 blocked, 9 caveated for CF Pages + adapter.


Does any item BLOCK Option A?

Item 7 (next/image) is a hard blocker unless you accept one of the workarounds.

The next/image optimizer is non-functional on CF Pages without a workaround. For Antlers, the affected surface is the public landing page (hero images). The workaround options are:

If the operator accepts unoptimized: true for the public landing page hero images, item 7 ceases to be a deployment blocker (though it remains a quality caveat). Antlers is primarily a dashboard app — the image-heavy surface is limited to the public landing page and marketing pages. The trading dashboard (the core product surface) uses lightweight-charts and data tables, not <Image>.

No item BLOCKS Option B (Vercel) or Option C (Heroku Node.js dyno). Both run standard Next.js on Node.js.


Option A viability: revised scoring

Taking the CF Pages audit findings against ADR-0105's D6 deployment dimension:

Items that are workaroundable (add engineering cost but not blockers): - RSC edge annotation (items 2, 3, 4, 8): mechanical per-route change; adds ~1 line per dynamic route; enforced via ESLint rule; low ongoing effort. - Server Action revalidation caveat (item 5): acceptable for settings forms; not acceptable for order-confirmation flows if those ever use Server Actions (they shouldn't — order flows are Raptor REST calls, not Next.js Server Actions). - Sentry server-side (item 9): workaroundable by accepting reduced server-side instrumentation on CF Pages. Client-side Sentry is fully functional. - Adapter version lag (item 10): manageable by pinning the adapter version in CI and running the adapter's compatibility check as a pre-upgrade gate.

Items that require a real tradeoff: - next/image (item 7): accept unoptimized: true (degrades public landing LCP) or add CF Images subscription (cost). Neither option is free.

Revised D6 score for Option A: 2/5 (down from ADR-0105's 3/5, reflecting the annotation burden and next/image gap which the original ADR did not fully quantify).


Recommendation

Option B (Vercel) is the recommended deploy target.

The reasoning:

  1. Zero adapter layer. Vercel builds Next.js. Every Next.js 14.2 feature works exactly as documented, with no polyfills, no edge annotation requirements, no version-lag risk. The Phase 0-C middleware design works out of the box.

  2. next/image works without a workaround. The public landing page hero images get full optimization (WebP conversion, responsive sizing, lazy load) at no additional cost.

  3. Sentry Next.js SDK fully supported. Server-side error capture for RSC, middleware, and Server Actions works without custom configuration.

  4. Phase 0 scaffold assumptions hold. PR #2895's next.config.mjs, app/layout.tsx, middleware design, and all Phase 0-B through 0-E sub-cards are written for standard Next.js App Router semantics. Vercel requires zero changes to these files.

  5. The "vendor lock-in" risk is manageable. Vercel provides export/egress compatibility: a Next.js app on Vercel can be moved to a Node.js dyno (Heroku, Fly.io, Railway) with next start if vendor lock-in ever becomes a concern. The migration is git push heroku main + setting Node.js buildpack — no code changes.

  6. CF DNS + Vercel hosting works cleanly. raxx.app DNS remains on Cloudflare (for DDoS protection, CF Access, geo-blocking). The Vercel deployment gets a CNAME to cname.vercel-dns.com. CF acts as a proxy in front of Vercel. This is a well-documented and production-proven pattern used by thousands of teams with Cloudflare proxying Vercel origins.

Caveats the operator should weigh:

Option C (Heroku Node.js dyno) is a valid fallback if Vercel billing or DNS coordination is unacceptable. The tradeoffs vs. Vercel: - Always-on dyno cost (~$7/month for Basic dyno). - No built-in CDN for static assets — must pair with CF Pages for static asset serving or accept the slightly higher latency for global users. - next start is the simplest possible deployment: heroku buildpacks:set heroku/nodejs, Procfile: web: npm run start. Zero adapter, zero configuration. App naming would follow the established convention: raxx-antlers-staging, raxx-antlers-prod.


Open questions for operator decision (#2886)

These must be resolved before Phase 1 sub-cards are groomed as ready-for-dev:

  1. Is Vercel billing acceptable? Hobby tier is free (with 100GB bandwidth/month limit and no custom domains on sub-paths — but raxx.app routed via CF proxy is fine). Pro tier is $20/month if limits are hit. Is this acceptable vs. Heroku dyno at ~$7/month but no built-in CDN?

  2. If CF Pages (Option A) is still preferred: Are you willing to accept unoptimized: true for next/image on the public landing page? This eliminates item 7 as a blocker. The rest of the caveats (edge annotation, Sentry server-side, adapter lag) are manageable. If yes, Option A becomes "viable with caveats" rather than "blocked."

  3. CF DNS proxy posture for Vercel origin: raxx.app is already on CF with full proxy. Vercel requires adding a CNAME at the CF DNS layer pointing to Vercel's edge. This is a 5-minute DNS change. Is the operator comfortable managing two CDN layers (CF in front of Vercel)?


Relation to ADR-0105

ADR-0105 §Risks Risk 1 stated: "audit which Next.js APIs the app uses against the adapter's compatibility matrix before starting Phase 1. If the adapter is insufficient, fallback deployment target is Vercel or a Heroku Node.js dyno."

This audit confirms: the adapter is not insufficient in a hard-stop sense (all features can be made to work with workarounds) but it imposes a maintenance tax and a concrete quality tradeoff on next/image. The fallback path — Vercel — is the cleaner choice for a solo-operator team that wants to move fast on Phase 1–3 without managing adapter compatibility overhead.

ADR-0105's D6 score for Vercel (4/5) vs CF Pages (3/5) is validated and, after this audit, the gap is wider than the original scoring reflected. The revised CF Pages score is 2/5 given the next/image gap and annotation burden.


This is a research memo, not a new ADR. The operator decision on deploy target closes issue #2886 and should be recorded as a comment there. Once the decision is recorded, the Phase 1 sub-cards can be groomed and dispatched.