@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
@cloudflare/next-on-pagesGitHub repo — README, supported/unsupported APIs list:https://github.com/cloudflare/next-on-pages- CF Pages Next.js documentation:
https://developers.cloudflare.com/pages/framework-guides/nextjs/ssr/ @cloudflare/next-on-pagesruntime compatibility table:https://github.com/cloudflare/next-on-pages/blob/main/packages/next-on-pages/docs/supported.md- Cloudflare Workers edge runtime API reference:
https://developers.cloudflare.com/workers/runtime-apis/ - Next.js 14.2 App Router docs — middleware, RSC, cookies, headers, Server Actions:
https://nextjs.org/docs/app - Sentry Next.js SDK — edge runtime support notes:
https://docs.sentry.io/platforms/javascript/guides/nextjs/ @cloudflare/next-on-pageschangelog:https://github.com/cloudflare/next-on-pages/blob/main/packages/next-on-pages/CHANGELOG.md- Cloudflare blog — "Full-stack Next.js on Cloudflare Pages":
https://blog.cloudflare.com/next-on-pages/ - Next.js
next/imagedocs — CF Pages known limitation:https://nextjs.org/docs/app/api-reference/components/image#loader - GitHub issue tracker —
next-on-pagesopen issues taggednext-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:
- Running
next buildto generate.next/ - Walking the build manifest to identify route handlers, page functions, middleware
- Re-bundling each route as a Cloudflare Worker function (Edge Runtime only)
- Emitting a
_worker.jsbundle + 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:
- 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. - The route or layout using
cookies()must be annotatedexport const runtime = 'edge'. (Same requirement as Feature 2.) 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 readingcookies()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:
- Progressive enhancement forms work. A
<form action={serverAction}>with a simple POST body (string fields) processes correctly in the edge runtime. - File uploads do not work.
FormDatawith file payloads uses theFileAPI, 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. - 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.
- Revalidation paths after a Server Action (
revalidatePath,revalidateTag) are partially supported.revalidatePathin 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),revalidatePathis 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_URL → https://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:
<Image src="..." />on a CF Pages site with@cloudflare/next-on-pageswill render a broken<img>tag or throw a build/runtime error unless a workaround is applied.- The Cloudflare Pages documentation explicitly acknowledges this limitation:
https://developers.cloudflare.com/pages/framework-guides/nextjs/ssr/#image-optimization
Available workarounds (all add engineering cost):
-
CF Images / Cloudflare Image Resizing: Use Cloudflare's own image transformation service as a custom
loaderfornext/image. This requires: - A Cloudflare Images subscription (additional cost) OR - Cloudflare Image Resizing (available on Pro plan+; $5/month for the plan) - Writing a customloaderfunction innext.config.mjs- Ensuring all images are served throughimages.raxx.appor an equivalent CF zone with Image Resizing enabled. -
Unoptimized mode: Set
unoptimized: trueinnext.config.mjsfor theimagesconfig. 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). -
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:
-
Performance tracing in edge runtime uses a different transport. The standard Sentry SDK uses Node.js's
httpsmodule for envelope transport. In edge runtime, it falls back tofetch()-based transport. This works on CF Workers sincefetchis available. -
Automatic server-side instrumentation (Sentry's
wrapApiHandlerWithSentry, page instrumentation) requires the adapter to support Sentry's patching pattern. The@cloudflare/next-on-pagesadapter 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/ -
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/cloudflarefor CF Pages deployments and lose some of the Next.js-specific instrumentation (page-load spans, server component tracing). -
The
NEXT_PUBLIC_SENTRY_DSNenv 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:
-
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).
-
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.
-
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.
-
next.config.mjsvsnext.config.ts: PR #2895 correctly usesnext.config.mjs(not.ts), noting that.tsconfig is a Next.js 15+ feature. The adapter supportsnext.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:
unoptimized: true— acceptable for a dashboard app where images are few; degrades LCP on the public landing page; low effort to implement.- CF Image Resizing — requires CF Pro plan ($20/month) or Cloudflare Images subscription; additional cost and configuration.
- Vercel / Heroku — zero workaround needed.
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:
-
Zero adapter layer. Vercel builds Next.js. Every Next.js 14.2 feature works exactly as documented, with no polyfills, no
edgeannotation requirements, no version-lag risk. The Phase 0-C middleware design works out of the box. -
next/imageworks without a workaround. The public landing page hero images get full optimization (WebP conversion, responsive sizing, lazy load) at no additional cost. -
Sentry Next.js SDK fully supported. Server-side error capture for RSC, middleware, and Server Actions works without custom configuration.
-
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. -
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 startif vendor lock-in ever becomes a concern. The migration isgit push heroku main+ setting Node.js buildpack — no code changes. -
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:
-
New billing relationship. Vercel's Hobby tier is free for personal use; the Pro tier is $20/month per member ($20/month for a solo operator). Given the personal-use-first launch posture, the Hobby tier may be adequate for the personal testing window. Upgrade to Pro when customer traffic begins.
-
CF Access service tokens + Vercel. The existing CF Access gate on
raxx.appwill continue to work — CF Access evaluates the request before it reaches the Vercel origin. The CF Access → Vercel origin flow requires a Vercel deployment URL as the CF Pages Worker route origin. This is standard and supported. -
CF Bot Fight Mode. Per
feedback_cf_access_does_not_bypass_bot_fight_mode.md: pair CF Access with a WAF skip rule. This applies equally whether the origin is CF Pages or Vercel — the WAF skip rule is at the Cloudflare layer, not the origin layer. No change to this posture.
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:
-
Is Vercel billing acceptable? Hobby tier is free (with 100GB bandwidth/month limit and no custom domains on sub-paths — but
raxx.approuted 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? -
If CF Pages (Option A) is still preferred: Are you willing to accept
unoptimized: truefornext/imageon 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." -
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.