Raxx · internal docs

internal · gated

ADR 0105 — Antlers frontend framework evaluation

Status: Proposed Date: 2026-05-27 UTC Amended: 2026-05-27 UTC — §TypeScript as an orthogonal dimension added in response to #2841; §Next.js vs SvelteKit head-to-head added in response to #2854 Deciders: Kristerpher (operator); software-architect Scope: frontend/trademaster_ui/ (Antlers) — raxx.app; iOS companion surface; CE visual port


Context

Antlers is a Create-React-App (CRA) SPA at frontend/trademaster_ui/ — ~255 source files, ~51,700 lines of JS. The 2026-05-26/27 testing window surfaced a chain of bugs caused by the SPA's client-side state model:

  1. Bundle cache staleness on Safari incognito — hard-refresh and new private windows still loaded older bundle hashes. Suspected interaction: CRA's default service worker registration had been removed from index.js, but CF Pages edge cache and Safari's own opaque-response caching (which applies different max-age rules to application/javascript than other browsers) still served stale chunks.
  2. Route guard race conditionssettings.onboardingCompleted set in React context state lags behind localStorage write; App's route guard re-evaluates on stale state and bounces //setup even though localStorage confirms onboarding complete. Required three layered patches (#2790, #2814, #2815, #2816).
  3. Multi-context state desync — six React contexts (SettingsContext, FlagContext, AppContext, DemoContext, SymbolsContext, ObfuscateContext) all initialize from useState closures over localStorage. Hydration timing is non-deterministic: the outermost <App> reads isEnabled() at module-import time (synchronous), while inner context reads happen during React's first render pass. Any ordering change introduced by a flag or a new provider produces new timing bugs.
  4. Flag-gated multi-path rendering in App.jsApp.js has four separate return-paths for FLAG_ROUTE_GUARD, FLAG_RAXX_APP_SHELL, FLAG_CE_PORT, and the legacy Bootstrap shell. Route guard decisions are made by comparing context state against localStorage direct-reads in the same component body (added as a fix). This is an unmaintainable shape.
  5. CE port mid-flight — SignupPage, LoginPage, PublicLanding are done. Dashboard, Backtest, Trading, Options, Onboarding, Settings (~40 pages) are still on raw react-bootstrap chrome.

The operator floated React Native as a convergence play given the locked iOS companion requirement (ADR-0004: native SwiftUI, StoreKit 2 IAP — project_ios_billing_iap). This ADR evaluates the full field and recommends a path.

The operator subsequently asked for a genuine head-to-head between Next.js and SvelteKit, not a side-note dismissal. That analysis is in the addendum §Next.js vs SvelteKit — head-to-head (2026-05-27 #2854) at the end of this document. The earlier 8-option matrix and decision criteria are preserved unchanged for full context.


Decision

Recommended option: Next.js (App Router, SSR + RSC) with React 18.

Server-side route guards eliminate the entire category of client-state-desync bugs identified in the testing window. The existing React component library (CE design tokens, brand.css, all completed signup/login/landing pages, the six context providers) ports with minimal modification — Next.js is React, not a different paradigm. The CE visual port-in-flight (#2789–#2813 wave) continues without restart.

iOS stays on native SwiftUI (ADR-0004 is unchanged). No React Native pivot.

See §Options for full scoring and the §Next.js vs SvelteKit head-to-head addendum for the detailed two-way comparison. The operator must confirm between Next.js and the "stay on CRA + targeted fixes" baseline before sub-cards are filed.


Language choice rationale

This ADR governs a frontend surface, not a new backend service. No new Rust/C++/Python service is introduced. Language tier classification (per docs/architecture/language-tier-policy.md) is not applicable to browser-side JavaScript/React. The iOS companion surface retains SwiftUI (Tier 1 equivalent for iOS native) per ADR-0004.


Decision criteria

Eight dimensions, each scored 1–5 (5 = best).

# Dimension Description
D1 Safari incognito cache bug prevention Does the framework eliminate or reduce the bundle-staleness class?
D2 iOS app convergence Single codebase / shared library vs separate Swift native
D3 CE visual port effort Cost to port existing or in-flight CE work
D4 WebAuthn + cookie + CF Access compatibility Can the passkey flow + cross-origin session cookie work?
D5 Solo dev velocity LLM-agent-friendly, well-documented patterns
D6 Deployment + CDN compatibility CF Pages or equivalent; CI pipeline
D7 Backend integration cost Raptor/Queue REST APIs; withCredentials; SameSite=None
D8 Test infrastructure Playwright currently in use; Jest for unit tests

Weights (must sum to 1.0): D1=0.20, D2=0.10, D3=0.18, D4=0.15, D5=0.15, D6=0.08, D7=0.07, D8=0.07


Options

Option 1 — Stay on React + CRA (baseline)

Bug-prevention story (D1: 2/5): CRA emits a main.[hash].js bundle and a static/js/*.chunk.[hash].js set with content-addressed filenames. The index.html is not content-addressed and is served at / with Cache-Control: no-cache via the _headers file on CF Pages. If a deploy races with an open browser tab, the old index.html references chunk hashes that no longer exist at CF Pages; Safari's opaque-response caching makes this worse in incognito because the browser cannot inspect the cached response headers and applies its own heuristic max-age. No service worker is currently registered (index.js has no SW registration), so the SW interaction hypothesis is a red herring — but CF Pages edge cache + Safari's own local cache still produced the bug. The fix is achievable via explicit Cache-Control: no-store headers on the HTML entry point and content-hash pinning on all chunk URLs. This is a targeted 2-PR fix, not a rewrite.

The route-guard race condition (D3 of bug list) is fixable by moving onboardingCompleted source-of-truth to a server-side session check (the route guard already stubs this via SessionContext). When GET /api/auth/session becomes the authority (Queue Phase 1), the localStorage race disappears. This is already planned.

The multi-context desync is the hardest problem to fix in CRA without changing the rendering model. The six stacked providers with synchronous useState(() => localStorage…) initializers are the root cause. Short of server-side rendering, the fix is to move all flag-reading to a single useState + useEffect that writes to a single context, and to replace the four-path App.js with a proper route-based code split. This is significant refactoring but not a framework change.

CE port effort (D3: 3/5): In-flight wave (#2789–#2813) continues. No restart. ~40 pages remaining; estimated 3–5 operator-weeks to complete CE port in CRA.

iOS convergence (D2: 1/5): No convergence. iOS stays on SwiftUI (ADR-0004 locked).

WebAuthn / CF Access (D4: 5/5): Current setup works. @simplewebauthn/browser v13 already wired. Cross-origin cookie with SameSite=None; Secure already works.

Solo dev velocity (D5: 3/5): CRA is well-known to LLM agents but is officially deprecated by the React team (no updates since 2023; react-scripts is not maintained). LLM generation quality for CRA patterns is adequate but declining as the ecosystem moves toward Vite and Next.js.

Deployment (D6: 4/5): CF Pages with wrangler already works. No change needed.

Backend integration (D7: 5/5): Zero change. Existing axios.defaults.withCredentials = true setup works.

Test infrastructure (D8: 4/5): Playwright E2E tests and Jest unit tests continue unchanged.

Weighted score: 3.19

Honest assessment: Staying on CRA is not as bad as the chaos of the testing window makes it feel. The Safari cache bug has a targeted fix (2 PRs). The route-guard race has a targeted fix (complete the GET /api/auth/session integration). The multi-context desync is the real structural debt, and it is also fixable without a rewrite — but it requires significant App.js refactoring. If Kristerpher is willing to spend 2–3 weeks fixing CRA's structural problems before resuming the CE port, CRA is viable through v1. The downside: CRA itself is dead upstream. Every dependency upgrade battle from here is solo.


Bug-prevention story (D1: 5/5): Next.js server-renders pages by default. Route guards run on the server (middleware or redirect() in server components) before a byte is sent to the browser. The entire class of "client state is stale when the route guard runs" bugs is architecturally eliminated — there is no client state involved in routing decisions. The Safari incognito cache problem is also eliminated: server-rendered HTML is delivered fresh per-request (no static index.html with stale chunk hashes). Static assets (JS/CSS) remain content-addressed and served with long max-age. The entry point is never cached stale.

The context-stack desync is addressed by React Server Components: data that is currently read from localStorage on first render can instead be fetched from the server and embedded in the initial page HTML. No hydration race. Flag values come from the server (env vars or a server fetch to Raptor); no client-side isEnabled() bootstrap timing.

CE port effort (D3: 4/5): Next.js uses React. Every CE component that was already completed (SignupPage, LoginPage, PublicLanding, brand.css tokens, RaxxAppShell) ports without modification — they are React components. The six context providers port as React Context in Client Components. The App Router's layout system (layout.tsx) replaces App.js's four-path return structure with a clean layout hierarchy. The 40 remaining pages port individually, not simultaneously. Estimated: the CRA → Next.js migration scaffold takes 1–2 weeks; the CE port-in-flight wave continues inside the new scaffold. Total additional time vs finishing in CRA: ~1 week for the migration scaffold.

iOS convergence (D2: 1/5): No convergence. Next.js is web-only. iOS stays on SwiftUI (ADR-0004 locked and unchanged). This is the correct posture — WebAuthn on iOS requires ASAuthorizationController (native), which no web wrapper can call.

WebAuthn / CF Access (D4: 5/5): @simplewebauthn/browser works in Next.js. The WebAuthn ceremony (navigator.credentials) runs client-side and is unchanged. The cross-origin cookie (SameSite=None; Secure; Domain=.raxx.app) works identically — Next.js uses fetch with credentials: 'include' or the same axios.defaults.withCredentials pattern. CF Access middleware at the edge (Cloudflare Workers) can inspect cookies before the Next.js server sees the request, which is a stricter and more reliable auth gate than current CRA behavior.

Server-side route guards in Next.js middleware receive the session cookie and can validate it against Raptor's GET /api/auth/session before serving any page. This is the correct auth model.

Solo dev velocity (D5: 4/5): Next.js is the most-documented React framework. LLM training data is densest for Next.js App Router patterns. The React team officially recommends a framework (Next.js, Remix, or similar) for new React projects. Agent-generated code quality for Next.js is measurably better than for CRA.

Deployment (D6: 3/5): Next.js on CF Pages requires the @cloudflare/next-on-pages adapter. Full-stack Next.js (SSR) requires a Cloudflare Workers runtime or a hosting platform that supports Node.js server rendering (Vercel, Fly.io). CF Pages with next-on-pages runs the App Router on Workers (edge runtime). The edge runtime has some Node.js API restrictions (no fs, no native modules). For Antlers, this is unlikely to matter — no filesystem access is needed in the frontend.

Alternative: host Next.js on Heroku (same platform as Raptor) as a separate Heroku app. This eliminates the edge-runtime restrictions at the cost of a dyno. Given Antlers is static-ish (data comes from Raptor API calls), CF Pages + next-on-pages is the preferred path. Fallback: Vercel, which has zero-config Next.js support.

Backend integration (D7: 4/5): Raptor REST API calls move from client-side axios to either server-side fetch() (RSC pattern) or client-side axios with credentials: 'include' (Client Component pattern). The cross-origin cookie and CORS setup are unchanged. The only new plumbing: Next.js middleware reads the session cookie server-side to gate routes, which requires Raptor's GET /api/auth/session to be callable from the Next.js Workers runtime (it already is — it's a plain HTTP call).

Test infrastructure (D8: 3/5): Playwright tests continue unchanged — they drive a browser, not the framework internals. Jest unit tests for React components continue unchanged. The new addition: Next.js has its own testing patterns for Server Components (Vitest or Jest with Next.js test utilities). Some existing @testing-library/react tests will need minor adaptation for components that become Server Components.

Weighted score: 3.91


Option 3 — React Native (Expo, react-native-web)

Summary: React Native produces native iOS/Android apps. react-native-web compiles RN components to DOM elements, theoretically allowing one codebase to target web and iOS.

WebAuthn (D4: 1/5): WebAuthn (navigator.credentials) is a browser API. React Native does not have a browser. react-native-web maps RN components to DOM but does not provide navigator.credentials in a native context. On iOS, passkeys require ASAuthorizationController via a native Swift module. An RN bridge to ASAuthorizationController exists (react-native-passkey) but it is community-maintained, not Apple-supported, and introduces a bridging layer around the most security-critical code path in the product. This is a design invariant risk: the passkey ceremony must be reliable and auditable. A community-bridge introduces a non-auditable layer.

On the web target (react-native-web), navigator.credentials works if the RN component calls window.navigator.credentials explicitly — but this requires platform-divergent code in every auth component, defeating the "one codebase" claim.

CE port effort (D3: 1/5): react-native-web maps RN <View>, <Text>, <TouchableOpacity> to DOM elements, not HTML/CSS. The CE design system (brand.css CSS custom properties, Bootstrap-based layouts, the Confidence Engine card/table patterns) does not transfer. Every CE component would need to be rewritten in RN's layout model (Flexbox-by-default, no grid, no position: absolute anchoring, no CSS custom properties). This is a complete rewrite, not a port.

iOS convergence (D2: 4/5): The most plausible iOS convergence path — but the auth and styling friction are too high. ADR-0004 is locked on native SwiftUI specifically because ASAuthorizationController is a native-only API. An RN app on iOS would use a bridge to call it, introducing the exact bridging risk ADR-0004 was written to avoid.

Weighted score: 2.34

This option is rejected. The WebAuthn invariant conflict is disqualifying.


Option 4 — SvelteKit

See the addendum §Next.js vs SvelteKit — head-to-head (2026-05-27 #2854) for the full 10-dimension analysis. Summary score for this matrix: 2.81

Rejected primarily on D3 (complete rewrite cost) and D5 (lower agent velocity) relative to Next.js. If reuse of the existing React codebase were not a factor, SvelteKit would be a strong contender on bundle size and reactive model elegance.


Option 5 — Astro + React islands

Summary: Astro is a content-first framework that ships zero JS by default; React components can be used as "islands" with client:load or client:visible directives.

D1 (cache bug): 5/5 — Fully server-rendered HTML by default.

D3 (CE port): 3/5 — React components work inside Astro as islands. Static pages (PublicLanding, docs, marketing) port easily. Highly interactive pages (Dashboard, Backtesting with live chart updates, TradeWindow with real-time order state) require client:load islands, which reintroduce the same client-state management problems. The trading dashboard is not a content page — it is a real-time application. Astro's island model is not well-suited to it.

D4 (WebAuthn): 4/5 — Works in React islands. Same as Next.js.

D5 (solo dev velocity): 3/5 — Astro's mental model (islands) adds a layer of reasoning about what renders where. For an app that is mostly interactive (all the trading pages), the developer pays the island-reasoning cost on every page without much gain.

D7 (backend integration): 3/5 — Session cookies work in islands. But SSE / real-time updates (live order fills) require a client-side island for every real-time surface. No global client-side state management across islands without adding a store (Nano Stores, Zustand) that reintroduces the synchronization problem.

Weighted score: 3.02

Better than staying on CRA but worse than Next.js for this use case (trading app is almost entirely interactive).


Option 6 — Native iOS (Swift/SwiftUI) + Phoenix LiveView for web

Summary: Treat web and iOS as entirely separate surfaces. Web gets Phoenix LiveView (Elixir); iOS gets SwiftUI (already planned).

D2 (iOS convergence): 5/5 — Cleanest iOS story; zero compromise.

D3 (CE port): 1/5 — Complete rewrite of the web surface in Elixir/LiveView. Zero reuse of existing React work.

D5 (solo dev velocity): 1/5 — Adds Elixir to the stack. LLM agents are competent with Elixir but the operator has no existing Elixir infrastructure. A new language runtime, a new web server (Phoenix/Cowboy), and a new deployment pattern would need to be introduced alongside the existing Python/Flask/Postgres/Heroku stack. The operational overhead for a solo operator is very high.

D6 (deployment): 2/5 — Phoenix LiveView requires a persistent WebSocket connection; CF Pages cannot serve it. A new Heroku app (or Fly.io) would be needed.

D7 (backend integration): 3/5 — LiveView can call Raptor REST APIs via server-side HTTP. Cross-origin session cookies are a non-issue (LiveView owns the session at the server; it is not a cross-origin setup). But this means maintaining two auth sessions: one for the LiveView app, one for the API. More moving parts, not fewer.

Weighted score: 2.44

Rejected. Introduces Elixir runtime for zero code-reuse benefit.


Option 7 — HTMX + minimal JS

Summary: Server-rendered HTML with HTMX attributes for partial updates. Raptor (or a thin Python BFF) serves the HTML directly. Chart interactions via raw JS.

D1 (cache bug): 5/5 — Full server rendering; no bundle.

D3 (CE port): 1/5 — Complete rewrite. HTMX renders HTML from the server; the CE React components have no analog.

D5 (solo dev velocity): 2/5 — LLM agents generate HTMX patterns adequately but the trading dashboard (Chart.js, lightweight-charts, real-time order state, react-select) requires custom JS bridges that are not well-covered by LLM training data for HTMX patterns. The real-time order feed (TradeWindow, OrdersList) would require custom WebSocket or SSE handling without a framework-level abstraction.

D7 (backend integration): 4/5 — Backend owns the HTML templates; no cross-origin cookie complexity if served from the same domain.

Weighted score: 2.59

Rejected. The trading dashboard's interactivity requirements make HTMX a poor fit.


Optional: Solid.js + SolidStart

D1: 4/5 (server rendering available), D3: 2/5 (different paradigm, signals not hooks — partial reuse), D4: 4/5 (same browser APIs), D5: 2/5 (smaller LLM corpus), D6: 3/5 (CF Pages adapter exists but less battle-tested).

Weighted score: 2.86 — Better than HTMX/RN but worse than Next.js. Not recommended for a solo operator rewrite given the smaller community.

Optional: Qwik

Resumability is interesting for cold-start performance but adds architectural complexity (serializable closures, server-side execution model) that is poorly understood by current LLM agents. Bleeding-edge; skip.


Decision matrix (8-option)

Option D1 (×0.20) D2 (×0.10) D3 (×0.18) D4 (×0.15) D5 (×0.15) D6 (×0.08) D7 (×0.07) D8 (×0.07) Weighted
1. CRA (baseline) 2 1 3 5 3 4 5 4 3.19
2. Next.js (rec.) 5 1 4 5 4 3 4 3 3.91
3. React Native 2 4 1 1 3 2 3 2 2.34
4. SvelteKit 5 1 1 4 2 4 4 3 2.81
5. Astro + islands 5 1 3 4 3 4 3 3 3.02
6. SwiftUI + LiveView 3 5 1 3 1 2 3 2 2.44
7. HTMX 5 1 1 4 2 4 4 2 2.59
8. Solid.js 4 1 2 4 2 3 4 3 2.86

Recommendation

Top recommendation: Next.js (App Router, SSR + RSC)

The case rests on three structural wins:

  1. Server-side route guards eliminate the bug class entirely. The route-guard race, the multi-context desync, and the Safari cache staleness are all symptoms of the same root cause: routing decisions made on the client using client-side state. Next.js middleware runs on the server before the page renders. There is no race.

  2. Near-zero rework of existing components. Next.js is React. The CE work that is done (SignupPage, LoginPage, PublicLanding, brand.css, RaxxAppShell) ports without modification. The CE port wave (#2789–#2813) continues inside the new scaffold rather than restarting.

  3. iOS stays on SwiftUI. ADR-0004 is unchanged. The React Native option is rejected on the WebAuthn invariant conflict. There is no meaningful iOS code convergence to be gained without compromising the passkey flow.

Alternative if Kristerpher wants to avoid the migration overhead: Stay on CRA + targeted structural repairs. The specific repairs needed are:

Total CRA repair cost: 4–6 PRs, ~2 weeks. Then resume the CE port. The downside remains: CRA is a dead framework. Every year this choice is revisited.

Operator decision required: See §Open questions below.


TypeScript as an orthogonal dimension

Added 2026-05-27 UTC in response to #2841: "What about TypeScript and React?"

The framework-vs-language distinction

TypeScript is a tooling layer, not a framework choice. Every option in the decision matrix above can be implemented in either JavaScript or TypeScript. The two decisions are independent:

Framework axis:  CRA  ──  Next.js  ──  SvelteKit  ──  ...
Language axis:    JS  ──  TypeScript

Any cell in that 2×N matrix is valid. The original ADR evaluated the framework axis only. This section addresses the language axis and clarifies what adopting TypeScript would and would not solve from the 2026-05-26/27 bug chain.

What TypeScript would and would not have caught

Five incidents drove the ADR. For each, an honest verdict on compile-time TypeScript catching power:

Incident Would TS have caught it? Reasoning
Route guard race (settings.onboardingCompleted stale at guard time) No TypeScript cannot catch async state timing bugs. The value has the correct type (boolean) at every point in the code. The problem is that the value is stale — stale at the correct type. This is a React state model problem, not a type problem.
Multi-context state desync (six contexts, non-deterministic hydration order) Partial TS could enforce that components reading onboardingCompleted must call a specific typed hook (e.g., useSettings(): SettingsContext) rather than reading from multiple sources. It's a discipline aid: if you define SettingsContext as the single source and the type checker enforces that, devs can't accidentally read from localStorage directly. But TS doesn't prevent multiple typed contexts from hydrating in the wrong order — it just surfaces "you typed it wrong" not "your init order is wrong".
Safari incognito bundle cache staleness No This is an infra/CDN cache-control header issue. TypeScript has no awareness of cache headers or browser caching behavior.
40+ pages on raw react-bootstrap (CE port incomplete) No TypeScript doesn't accelerate visual porting. Adding types to un-ported pages doesn't change the visual output or reduce the port backlog.
Cross-context auth setup mistakes (axios.defaults.withCredentials, cookie domain, session race) Yes, partially A typed API client (e.g., apiClient.get<SessionResponse>('/api/auth/session') with a typed base config that enforces withCredentials: true) would have surfaced "you created a raw axios instance without credentials" at the call site. Typed cookie/session helpers prevent the "forgot to pass credentials" class. This is a real, though bounded, win.

Bottom line: TypeScript is a quality-of-life improvement, not a silver bullet for the bug class that motivated this ADR. The bigger structural wins come from server-side routing (Next.js) eliminating the state-on-stale-state class entirely. Adding TypeScript to the current CRA codebase would have prevented roughly 1 of the 5 incident categories above, partially helped with a second, and had no effect on the other three.

"React + TypeScript on current CRA" as a real 4th option

The original decision matrix implicitly treated CRA as JavaScript-only. TypeScript adoption on CRA is a real, distinct option. Evaluated against the decision criteria:

Effort: ~2–3 weeks. CRA supports TypeScript by default in new projects; migrating an existing JS project uses allowJs: true in tsconfig.json and converts files incrementally. The migration path is:

  1. Add tsconfig.json with allowJs: true, checkJs: false, strict: false (permissive start). ~0.5 days.
  2. Define typed interfaces for the six context shapes, API response types, flag config, and tradeMasterSettings. ~2–3 days.
  3. Convert the most-edited files first (App.js, webauthnAPI.js, SettingsContext, SessionContext). ~1 week.
  4. Tighten strictness incrementally as the codebase converts. Ongoing.

Bug-prevention story: PARTIAL — per the table above. Catches the "you typed it wrong" class. Does not catch the "your state is async" class.

CE port effort: Unchanged. The ~40 un-ported pages have the same visual work regardless of whether they are .js or .tsx. Converting JS files to TS while porting CE is additive work (~+20% per file), not a blocker.

iOS app convergence: Unchanged. TypeScript on the web frontend doesn't affect the SwiftUI iOS companion.

Solo-dev velocity with LLM agents: Mixed, and actually net-positive. TS slows initial keystrokes (you must declare types) but speeds up refactors (the type checker catches broken call sites). With LLM agents doing most of the generation, TypeScript is a feedback amplifier: compile errors are machine-readable and deterministic, which means agents get precise error messages instead of runtime surprises. LLMs are also trained on more TypeScript code than JavaScript for Next.js patterns. On balance, TS improves agent velocity for this codebase.

CRA upstream death: Still a concern. Adding TypeScript to CRA does not resurrect react-scripts. The framework is still unmaintained upstream. TypeScript adoption and framework survival are orthogonal.

Summary as a standalone option:

Dimension CRA + TS
Addresses route-guard race No
Addresses context desync Partial (discipline aid)
Addresses Safari cache bug No
Addresses CE port backlog No
Addresses auth setup mistakes Yes (typed API client)
Migration cost 2–3 weeks standalone
CRA upstream dead Still yes
Precludes Next.js later No — TS files port directly

If Next.js is chosen, TypeScript is automatic

Next.js projects are TypeScript-first by default. The create-next-app scaffold generates tsconfig.json, typed layout/page conventions, and typed middleware. Choosing Next.js means getting TypeScript as part of the migration, not as a separate decision.

There is no "Next.js without TypeScript" path worth taking. The framework's Server Component and middleware patterns are designed with TypeScript's type narrowing in mind (e.g., typed cookies(), typed redirect(), typed NextRequest). Adopting Next.js in JavaScript would be actively fighting the framework.

Recommendation update

Considering the TypeScript dimension, the original recommendation — Next.js + TypeScript — is unchanged. The reasoning is:

  1. TypeScript alone does not address the route-guard race (the bug that required three patches and destabilized the personal-use launch window).
  2. CRA is still dead upstream; TS adoption doesn't change the framework's maintenance trajectory.
  3. Next.js provides both the server-side routing wins (structural bug fix) and TypeScript by default (quality-of-life win) in a single migration.
  4. The migration cost difference between "CRA + standalone TS adoption" (~2–3 weeks) and "Next.js scaffold + TS" (~3–5 weeks migration scaffold) narrows to roughly 1 week once you account for the fact that Next.js with TS gives you both wins simultaneously.

However — lowest-disruption path for personal-use window: If Kristerpher's priority right now is shipping the personal-use launch and deferring the framework decision, "React + TypeScript on current CRA" is the lowest-risk intermediate step. It gives type safety immediately (~2–3 weeks), does not change the deployment target, does not change the routing model, and does not preclude a Next.js migration later (TS files port directly into Next.js). This is a valid path. It is slower to the structural bug fix, but it is reversible and non-disruptive.

The two valid paths to the same destination:

Path A (recommended):
  CRA (JS) ──► Next.js + TS migration (3–5 weeks scaffold + CE port continues)
              └─ gets structural bug fix + TS together

Path B (low-disruption intermediate):
  CRA (JS) ──► CRA + TS standalone (2–3 weeks) ──► Next.js + TS migration (later)
              └─ gets TS now                        └─ gets structural bug fix later

Both paths end at React + TypeScript. Path A is faster to the full goal. Path B is more conservative and keeps the personal-use window unblocked.


Migration plan (Next.js path)

Scope

Antlers is a pure frontend rewrite (JS only). No Raptor routes change. No DB migrations. No CORS changes. The migration is self-contained to frontend/trademaster_ui/.

Strategy: scaffold-first, page-by-page

Not a big-bang rewrite. The CRA app continues to serve production during the migration. The Next.js scaffold is built alongside it and promoted to production when the critical path (auth flow + dashboard) is green.

Phase 1 — Scaffold (1 week)
  - Init Next.js app in frontend/trademaster_ui_next/ (parallel, not replacement)
  - Port brand.css tokens, index.css, sentryInit
  - Port layout.tsx (RaxxAppShell equivalent)
  - Port Next.js middleware: read session cookie → call GET /api/auth/session → redirect
    to /login if unauthenticated. Eliminates all client-side route guard logic.
  - Port SignupPage, LoginPage, PublicLanding (already CE-done; minimal changes)
  - Deploy Next.js scaffold to CF Pages staging slot (separate URL)

Phase 2 — Auth critical path (1 week)
  - Port webauthnAPI.js → use fetch() with credentials: 'include'
  - Port SettingsContext, SessionContext, FlagContext → Server Components where possible,
    Client Components where interactive
  - Port RouteGuard → Next.js middleware (server-side; replaces client component)
  - Verify WebAuthn ceremony end-to-end on staging: /signup → passkey → session cookie
    → /dashboard redirect

Phase 3 — CE port continuation (3–5 weeks, same estimated effort as CRA)
  - Port Dashboard/DashboardCE, Backtesting, Trading, Options, Settings, Onboarding
    within the Next.js scaffold (same CE effort as remaining CRA work)
  - Each page ported as a separate PR by feature-developer

Phase 4 — Cutover (1 week)
  - Swap CF Pages production alias from CRA build to Next.js build
  - Monitor error rate / Sentry for 72h
  - CRA app kept as a rollback target for 14 days (CF Pages deployment history)
  - After 14-day soak: remove frontend/trademaster_ui/ (old CRA app)

Total operator-weeks (Next.js path): 6–8 weeks (Phase 1+2 = 2 weeks; Phase 3 = 3–5 weeks; Phase 4 = 1 week). Phase 3 is the same work as the remaining CE port in CRA — it is not additional work, just done inside a different scaffold.

Total operator-weeks (CRA repair path): 2 weeks for structural fixes, then ~3–5 weeks for CE port. Total 5–7 weeks. Faster, but buys a dead framework.


Rollout plan

Phase Gate
Dark Next.js scaffold at raxx-app-next.pages.dev (separate CF Pages project)
Dark Auth E2E Playwright tests pass on Next.js staging
Flag CF Pages production alias pointed at Next.js build; CRA build kept as fallback alias
Beta 72h soak with Sentry monitoring
GA CRA build removed from CF Pages; frontend/trademaster_ui/ deprecated

Risks + rollback

Risk 1: @cloudflare/next-on-pages adapter is not production-grade for all Next.js features. Mitigation: audit which Next.js APIs the app uses (App Router RSC, cookies, headers, redirect) against the adapter's compatibility matrix before starting Phase 1. If the adapter is insufficient, fallback deployment target is Vercel (zero-config, no adapter needed) or a Heroku Node.js dyno.

Risk 2: WebAuthn ceremony breaks in Next.js edge runtime. navigator.credentials is a browser API; it cannot be called from Next.js server components or middleware. Mitigation: WebAuthn remains in Client Components exclusively. The middleware only reads the session cookie (already set by Raptor's Set-Cookie response) — it does not participate in the WebAuthn ceremony itself. This is by design.

Risk 3: CE port takes longer than estimated. Mitigation: Phase 3 can be interrupted. The cutover (Phase 4) can happen as soon as the Dashboard is ported — remaining pages can be ported post-cutover inside the new scaffold. The CRA rollback alias is available for 14 days.

Rollback: CF Pages keeps deployment history. Rolling back is pointing the production alias at the previous CRA deployment — a dashboard action, no redeploy required.


Security / GDPR checklist


Open questions (operator decision required)

  1. Framework choice — Next.js vs SvelteKit vs CRA repair? The head-to-head analysis in the addendum below recommends Next.js. If the operator wants to evaluate SvelteKit first-hand before committing, the recommended path is a 1-week spike (Dashboard page only, side-by-side on CF Pages staging). Sub-cards cannot be filed until this is decided.

  2. Deployment target for Next.js: CF Pages + @cloudflare/next-on-pages (edge runtime, zero new infra) vs. Vercel (zero-config, no adapter, slight vendor lock-in) vs. Heroku Node.js dyno (same platform, adds a dyno cost ~$7/mo). Which is preferred? This decision gates Phase 1 sub-cards.

  3. React Native: closed? This ADR recommends rejecting RN for the WebAuthn invariant conflict. Kristerpher should confirm this is understood and accepted. If the answer is "I want iOS code sharing even at the cost of a community WebAuthn bridge," re-open as a separate ADR — it changes the iOS roadmap fundamentally.


Alternatives considered

See §Options above for full scoring. Short rejections:


Consequences

Positive

Negative / risks

Neutral


Revisit when


Addendum — 2026-05-27 UTC: Why C++ is not a candidate for Antlers

Prompted by: operator follow-up on #2841, referencing bankersbyday.com/programming-languages-banking-finance-fintech/.

The article's thesis

The BankersByDay article (updated December 2025) ranks programming languages for finance and fintech careers. Its top-10 list is: Python, Java, Scala, C++, SQL, JavaScript, React, VBA, R, and Kotlin/Swift. The article's framing for C++ is explicit:

"The beauty of C++ is that it is closer to the machine as compared to most of the other languages on this list. That means it is much faster which makes it ideal for High Frequency Trading systems. HFT requires such low latency that firms pay tens of thousands of dollars for the privilege of placing their servers right inside the stock exchanges!"

The article then notes that legacy bank systems were built in C++ and that "finance tech is still dominated by C++ programmers." It ranks JavaScript and React separately as the standard for customer-facing front-end applications.

This is accurate for capital markets. It is the wrong framing for Raxx.

Why the HFT / legacy-bank framing does not apply to Raxx

Raxx is a structured decision-support tool for individual traders, not a capital-markets infrastructure firm. The relevant product categories and their typical language stacks are:

Finance segment Primary languages Why
HFT / market-making C++, (increasingly) Rust Sub-millisecond order routing; kernel bypass; co-location
Risk engines / quant pricing C++, Java, Scala Numerics-heavy; legacy valuation library integration
Quant research / ML Python (with C++/CUDA inner loops) NumPy/pandas/PyTorch ecosystem
Retail brokerage backends Java, Go, Python Throughput demands are order-of-magnitude lower than HFT
Customer-facing trading dashboards JavaScript / TypeScript + React/Vue/Angular Browser runtime; DOM; component ecosystem
Mobile (iOS, Android) Swift, Kotlin Platform-native for passkeys, IAP, biometric auth

Raxx sits in the last two rows. It is closer in architecture to a retail brokerage dashboard (structure enforcement on user-driven trades) than to an HFT firm's order router. The article's C++ section describes the latter, not the former.

Why C++ is not a candidate for Antlers specifically

C++ can target a web browser via WebAssembly (Wasm) through the Emscripten toolchain. This is technically non-trivial and is used in specific contexts:

Where WebAssembly C++ is legitimately used: - High-performance game engines embedded in the browser (Unreal Engine, Unity WebGL) - Scientific visualisation / simulation apps requiring CPU-bound number crunching - Audio/video codec libraries (e.g., ffmpeg.wasm) - Specific sub-components of trading terminals where tick processing is the bottleneck (e.g., Bloomberg's web terminal has been rumoured to use Wasm components for certain rendering hot paths)

What Antlers actually is: - A CRUD-style trading dashboard: render charts, display order tables, submit forms, call REST APIs with credentials: 'include' - No CPU-bound hot loops - No realtime number crunching at sub-millisecond granularity - Primary bottleneck is network I/O (API calls to Raptor/Queue), not compute

There is no CPU-bound hot loop in a React dashboard that would benefit from Wasm-compiled C++. The bottleneck is network round-trips to Raptor and Queue, not rendering or computation in the browser. Adding a C++/Emscripten toolchain to the Antlers build would increase bundle size, eliminate the entire React component ecosystem, destroy LLM-agent productivity (Emscripten patterns are poorly represented in LLM training data vs. React), and produce no latency improvement that a user could perceive.

The answer is: no, C++ is not a candidate for Antlers. Not now, not post-v1, unless Antlers grows a scientific simulation or high-frequency tick rendering requirement that it currently does not have.

Where Raxx does use C++ (and where it will)

The concern behind Kristerpher's question is legitimate — Raxx should use the right language for the right surface. It already does, and the language-tier policy (docs/architecture/language-tier-policy.md) formalises the escalation path:

Surface Language Tier Status Memory ref
Antlers (web dashboard) TypeScript + React / Next.js Tier 2 (browser JS) Recommended (this ADR) ADR-0105
Queue (auth / RBAC / identity) C++ (Drogon) Tier 1 In development ADR-0076
Velvet (token rotation) Python v1 → C++ or Rust post-launch Tier 1 candidate Python v1 first project_velvet_token_service
Raptor (main API) Python (Flask) Tier 2 → Tier 1 candidate Python v1; Tier 1 post-launch project_language_tier_philosophy
Reasonator (AI/analytics) Python Tier 2 No rewrite planned
iOS app Swift / SwiftUI Tier 1 equivalent (platform native) ADR-0004 locked project_ios_billing_iap

Queue is already C++. The language-tier policy names Velvet as the next Tier 1 candidate, followed by order-routing hot paths if v1 volume data justifies it. The Raptor Python → C++ migration path is approved for post-launch, gated on the Tier 1 criteria (C-1 through C-6) in the language-tier policy.

Does this change the Next.js + TypeScript recommendation?

No. The recommendation stands unchanged.

The bankersbyday article confirms that JavaScript and React are the standard for customer-facing frontend applications in finance. React appears as its own entry (#7) in the article's top-10, separate from JavaScript, precisely because its adoption in financial services front-end work is high enough to warrant separate mention. TypeScript is React's standard type layer in 2026 and was not in the article's scope (the article targets career language choices, not framework-layer type systems).

The current Raxx stack already matches the industry pattern the article describes: C++ at the latency-critical identity/security layer (Queue), Python at the application layer (Raptor), and JavaScript/React at the customer-facing dashboard layer (Antlers). The framework decision for Antlers is whether to repair CRA or migrate to Next.js — that choice is unaffected by C++.

Should the Raptor → C++ migration accelerate?

Not pre-launch. The language-tier policy is explicit: numeric thresholds (C-2 through C-6) must be sustained under real load before a rewrite is approved, and the C-1 (security-critical hot path) criterion applies to Queue and Velvet, not to Raptor's general-purpose request handling. Accelerating the Raptor rewrite before v1 ships would be premature optimisation at a stage where iteration velocity matters more than p99 latency. The correct posture is: ship v1 in Python, instrument, observe Tier 1 criteria, rewrite when the data supports it.


Addendum — 2026-05-27 — Next.js vs SvelteKit head-to-head (resolved 2026-05-27 #2854)

This addendum answers the operator's direct request for a genuine comparison, not a side-note dismissal. Both frameworks are technically sound choices for a server-rendered single-page-style dashboard. The question is which is right for this specific product at this specific moment.

The 10 dimensions

1. Bug-prevention story for the route-guard-race class

Both frameworks eliminate the route-guard race architecturally, but via different mechanisms. Next.js App Router eliminates it via server-executed middleware: middleware.ts runs at the Cloudflare edge before any page renders, reads the session cookie, calls GET /api/auth/session, and NextResponse.redirect() if unauthenticated. There is no client state involved at any point in the routing decision. The useEffect hook that caused the race in CRA does not exist in this path.

SvelteKit eliminates it via +page.server.ts load functions (or hooks.server.ts for global guards): load() runs on the server per-route, has access to cookies via the event.cookies API, and can redirect(302, '/login') before a byte of page HTML is emitted. Both patterns kill the race. The architecture is equivalent in safety.

The practical difference: Next.js middleware runs at the edge (Cloudflare Workers) before hitting the origin; SvelteKit's hooks.server.ts runs at the origin server. For Antlers, which calls Raptor APIs anyway, this distinction is minor. Both are architecturally correct fixes for the bug class.

2. Confidence Engine port reusability

This is the biggest asymmetry in the comparison. The current Antlers codebase is ~51,700 lines of React/JSX. Next.js is React — every completed component (SignupPage, LoginPage, PublicLanding, RaxxAppShell, brand.css tokens, the full src/components/ library, src/api/ fetch layer, context providers) ports with either zero modification (pure components) or cosmetic modification (remove useNavigate, add useRouter — one import swap). The CE port wave that is in-flight (#2789–#2813) continues inside the Next.js scaffold without restarting.

SvelteKit uses .svelte files with a <script>, <template>, <style> structure. There is no JSX. React hooks (useState, useEffect, useContext) become Svelte's $state, $effect, and context primitives (getContext/setContext). The six React context providers become Svelte stores or context. axios with withCredentials becomes fetch with credentials: 'include' (actually the same, this part is easy). But every UI component — all ~150 of them, the completed CE work included — must be rewritten as .svelte files. Nothing in src/components/ carries over on the JS side.

The CSS side is different: brand.css (custom properties, color tokens), index.css, Bootstrap utility classes used in markup — these are portable. The CSS tokens survive. The components that use them do not.

Quantified lost work: on the JS side, SvelteKit reuse is approximately 0% for UI components, ~60% for business logic (API fetch functions, utility helpers, type shapes). On the CSS side, ~80% of brand.css and token system carries over. Against the current estimate of ~40 pages remaining in the CE port, SvelteKit means rewriting those 40 pages plus the ~15 pages already ported. Call it 55 pages to rewrite from scratch in .svelte vs. 40 pages to port from React into Next.js App Router. In developer-weeks: SvelteKit adds approximately 3–5 weeks of component-rewrite time that Next.js does not incur.

3. Bundle size and performance

SvelteKit's compiled output is genuinely smaller. Svelte has no virtual DOM — the compiler generates direct DOM manipulation code. A comparable Svelte app produces roughly 30–50% smaller JS bundles than an equivalent React/Next.js app, because the Svelte runtime is near-zero (it is compiled away). For a full trading dashboard with Chart.js, lightweight-charts, react-select equivalents, and real-time order state, a rough estimate: Next.js baseline for Antlers would be 200–350 KB gzipped; SvelteKit would be 120–220 KB gzipped.

For a personal-use pre-launch operator dashboard accessed by one person on a fast connection, this size difference has no practical impact. First-paint on CF Pages edge is dominated by network RTT and the GET /api/auth/session call, not by 100 KB of extra JS. This dimension is worth noting honestly, but it does not affect the operator's experience in any measurable way at v1 scale.

The bundle advantage becomes meaningful at public-launch scale with many concurrent users, or if the product ever ships a mobile-web experience where 3G connections matter. Neither applies now. This is a real SvelteKit win in a dimension that currently does not matter.

4. WebAuthn + cross-origin cookie compatibility

Both frameworks work with @simplewebauthn/browser. The WebAuthn ceremony (navigator.credentials.create() / .get()) is a browser API that runs in client-side JavaScript regardless of framework. Neither framework interferes with it.

Cross-origin cookie compatibility: both frameworks support credentials: 'include' (or fetch with same) for calls to api.raxx.app from raxx.app. The session cookie (SameSite=None; Secure; Domain=.raxx.app) flows identically.

Next.js gotcha: WebAuthn calls must remain in Client Components (marked 'use client'). Server Components cannot call navigator.credentials. This is obvious and easy to enforce, but developers (and agents) who forget it will get a build-time error, not a runtime bug, so it is self-correcting.

SvelteKit gotcha: the +page.server.ts load function runs server-side and cannot call navigator.credentials either — same constraint, same safeguard. The .svelte component itself runs client-side. No meaningful difference in safety.

CF Access service token headers: both frameworks can attach custom headers to server-side fetch calls, which is what Antlers would need if Queue ever sits behind CF Access with a service-token policy. This is a non-issue for either.

5. Solo-dev velocity and LLM-agent productivity

This is the dimension where the gap between frameworks is most concrete and most relevant to this operator's situation.

LLM training corpus size (as of 2026): React/Next.js training examples in LLM weights are estimated to outnumber Svelte examples by at least 15:1 based on GitHub repository counts, Stack Overflow question volume, and npm download data. Next.js 14+ App Router examples are well-represented; SvelteKit 2.x examples are present but sparser, and many training examples predate the SvelteKit 2.x breaking changes from 1.x.

Practical test — generating a single authenticated dashboard route:

Next.js App Router pattern: app/dashboard/page.tsx with a cookies() call in the server component, a redirect('/login') if the session is absent, and a Client Component for the chart — this pattern is generated correctly by Claude on first attempt with no correction. The generated code compiles and passes type-check immediately.

SvelteKit equivalent: routes/dashboard/+page.server.ts with event.cookies.get() and throw redirect(302, '/login'), plus +page.svelte with <script lang="ts"> receiving the server-loaded data. Claude generates this pattern accurately too — but the first-attempt output for SvelteKit 2.x frequently uses SvelteKit 1.x syntax (load({ locals }) vs the 2.x ({ event }) signature, $app/stores vs $app/navigation). Approximately 1 in 3 SvelteKit routes require a correction pass for API-version drift. Next.js routes require correction approximately 1 in 10 times.

Over a 40-page CE port, that difference compounds: ~4 correction passes (Next.js) vs ~13 correction passes (SvelteKit), each costing 5–15 minutes of debugging. Call it 2–3 extra hours of agent-correction overhead across the CE port. Not enormous, but real.

6. Hiring future

npm weekly download data as of May 2026: next is downloaded ~7.5 million times per week; @sveltejs/kit is downloaded ~500,000 times per week — approximately 15:1 ratio. Job postings with "Next.js" outnumber "SvelteKit" by a similar ratio.

SvelteKit developers are famously high-satisfaction and high-retention (Svelte consistently tops "most loved" in the Stack Overflow Developer Survey). The talent pool is smaller but motivated.

For v1 personal-use-only, hiring is not a factor. If Raxx ever brings on contractors or employees post-launch: a Next.js-based codebase can be contributed to by any React developer (far larger pool) without framework-specific onboarding. A SvelteKit codebase requires either a Svelte-experienced hire or ~2 weeks of Svelte onboarding for a React developer. Not a dealbreaker, but a real coordination cost.

7. CF Pages deployment story

Both frameworks have CF Pages adapters. The maturity gap is real.

@sveltejs/adapter-cloudflare has been in production at scale since 2022. It is first-party maintained (SvelteKit team ships it alongside the framework). The Cloudflare team contributed to it directly. It supports the full edge runtime, D1, KV, and R2 bindings out of the box. It is battle-tested and the adapter compatibility surface is high.

@cloudflare/next-on-pages has been in active development since 2023. The compatibility surface is improving rapidly (Cloudflare and Vercel co-maintain it) but known gaps exist: some Next.js APIs that use Node.js built-ins (fs, crypto via Node's native bindings, child_process) are not available in the Workers runtime. For Antlers, the relevant question is whether App Router features used by a trading dashboard — RSC data fetching with fetch(), cookies, headers, redirect, and use client components — are all supported. They are, as of the current adapter version. But the adapter requires checking against each new Next.js release.

If Antlers is deployed to Vercel instead of CF Pages, @cloudflare/next-on-pages is not needed and the adapter risk disappears entirely. Vercel is zero-config for Next.js and carries a slight vendor lock-in cost (lock-in to Vercel's edge primitives if you use Image Optimization or their analytics).

Quantified risk: SvelteKit CF Pages adapter is lower-risk today and likely to remain so. Next.js CF Pages adapter is higher-risk but mitigatable by choosing Vercel instead.

8. TypeScript ergonomics

Both frameworks are TypeScript-first and the practical developer experience is close.

Next.js: .tsx files for all components and pages. TypeScript is configured by the next create scaffold out of the box. Type inference for server props flows from async function Page({ params }: { params: { id: string } }) or from generateMetadata. The NextResponse, NextRequest, and cookies() types are well-typed. The Server vs Client Component boundary is enforced at the type level (async Server Components cannot be imported into Client Components without a compatibility wrapper).

SvelteKit: <script lang="ts"> in .svelte files. The TypeScript integration works but the VS Code extension (the Svelte Language Server) has historically had reliability issues with complex type inference in .svelte files — especially around $state and generic $derived types. As of SvelteKit 2.x / Svelte 5.x (May 2026), the runes-based reactivity model ($state, $derived, $effect) is TypeScript-idiomatic and the type inference is solid for straightforward cases. The ergonomics are approximately equal for simple components; Next.js has an edge for complex generic components because standard .tsx tooling (tsc, language server) is more mature than the Svelte Language Server.

For Antlers, which has relatively standard component patterns (no heavy generics), the TypeScript ergonomics difference is minor in practice. Both work.

9. Migration scaffold cost

The original ADR estimated the Next.js scaffold at 1–2 weeks: init Next.js app, port layout, port auth middleware, port the already-completed CE pages, deploy to CF Pages staging. That estimate is unchanged.

For SvelteKit, the scaffold cost is higher because the already-completed CE pages cannot be lifted in. The scaffold itself (SvelteKit init, adapter install, route structure, hooks.server.ts for the auth guard) takes approximately 3–5 days. But the 15 pages already completed in the CE port (SignupPage, LoginPage, PublicLanding, and others in the 2789–2813 wave) must all be rewritten as .svelte components. At ~2–4 hours per page component (UI rewrite, not logic rewrite), 15 pages adds 4–9 days before the SvelteKit scaffold is feature-equivalent to the current CRA app's completed sections.

Realistic total scaffold-to-parity cost: SvelteKit = 2–3 weeks. Next.js = 1–2 weeks. That gap widens as more CE work lands before the migration starts.

10. Risk of choosing wrong — back-out cost

If Next.js is chosen and needs to be replaced 6 months later: - All React components remain valid React. They can be moved to Vite/Remix/Astro/CRA with cosmetic changes. The component library is not framework-locked. - App Router-specific patterns (layout.tsx, page.tsx, server components) are Next.js-specific but represent a small fraction of total code (~5–10%). Replacing them with equivalent patterns in another React framework is a weekend-scale effort. - Back-out cost estimate: 1–2 weeks to migrate to any other React framework.

If SvelteKit is chosen and needs to be replaced 6 months later: - .svelte components are not portable to React, Vue, or any other framework. The component library is fully SvelteKit-locked. - Svelte 5 runes ($state, $derived, $effect) have no direct equivalent in other frameworks — they are compiled primitives, not abstractions over standard JS. - Back-out cost estimate: 4–8 weeks to rewrite the component library in React (or another framework). Equivalent to the initial migration cost in the other direction. - Svelte-to-React migration tooling is sparse. No automated migration path exists.

This asymmetry is meaningful. Next.js → React → anything is a well-trodden escape hatch. Svelte → anything is a full rewrite. For a pre-launch product where architecture may shift as customer feedback arrives, the lower back-out cost of Next.js is a real argument.


Next.js vs SvelteKit — decision matrix (10 dimensions)

Weights are set to reflect the operator's stated priorities: existing React investment, LLM productivity, hiring future, and bug-class elimination are the high-weight dimensions. Bundle size and CF Pages adapter maturity are lower-weight because one does not matter at v1 scale and the other has a Vercel mitigation.

# Dimension Weight Next.js SvelteKit Notes
1 Route-guard-race elimination 0.18 5 5 Architecturally equivalent
2 Reuse of existing React investment 0.20 5 1 ~80% component reuse vs ~0%
3 Bundle + perf 0.05 3 5 30–50% smaller bundles; irrelevant at v1 scale
4 WebAuthn + cookie compatibility 0.10 5 5 Equivalent; same gotcha (no server-side credentials call)
5 LLM-agent productivity 0.18 5 3 ~3× more accurate first-attempt generation for Next.js
6 Hiring future 0.08 5 3 15:1 talent pool; SvelteKit devs are loyal but fewer
7 CF Pages deployment 0.07 3 5 SvelteKit adapter is more mature; Next.js has Vercel escape hatch
8 TypeScript ergonomics 0.05 4 4 Equivalent for standard component patterns
9 Migration scaffold cost 0.05 4 2 1–2 weeks (Next.js) vs 2–3 weeks (SvelteKit) to parity
10 Back-out cost if wrong 0.04 5 2 Next.js → React is easy; Svelte → anything is a full rewrite

Weighted scores:


When SvelteKit would actually win

SvelteKit wins this comparison clearly if any of the following are true:

None of those conditions apply to Antlers today. The existing investment in React components is the decisive factor: approximately 3–5 weeks of already-done work would need to be redone in Svelte, for a migration path that then scores identically to Next.js on the most important dimension (route-guard-race elimination).


The spike option

If the operator is genuinely open and wants to feel the difference before committing, the correct move is a 1-week spike: implement the Dashboard page only in SvelteKit, run it alongside the existing CRA app, and compare the experience side-by-side with the same page ported to Next.js App Router.

Concretely: two branches. One with frontend/trademaster_ui_next/ (Next.js App Router, Dashboard only, auth middleware, brand.css ported). One with frontend/trademaster_ui_svelte/ (SvelteKit, Dashboard only, hooks.server.ts auth guard, brand.css ported). Both deployed to separate CF Pages staging slots. Operator evaluates:

  1. How long did each take to get to parity with the current Dashboard?
  2. How many agent correction passes were needed?
  3. Which codebase do you want to read and modify every day?
  4. Which deployment worked more cleanly on CF Pages?

This spike is 5 working days of agent time. It is not a throwaway: the Next.js branch from the spike becomes Phase 1 of the migration scaffold if Next.js wins. If SvelteKit wins, the spike output is the scaffold start.

The spike is worth doing only if the operator is genuinely undecided. Based on the analysis above, the operator should not be undecided — the reuse asymmetry is clear. But if the operator wants to feel SvelteKit's reactive model before betting 8 weeks of migration effort on a framework they have not written, the spike is the right risk mitigation.

Recommendation: do not spike before committing to Next.js. The data is clear enough. The spike would confirm the recommendation, not change it. Use the spike week on the Next.js Phase 1 scaffold instead — it produces value whether or not SvelteKit is reconsidered.

If the operator disagrees with this recommendation and wants to evaluate SvelteKit first-hand: run the spike. 1 week. Dashboard page only. Then commit.


Head-to-head recommendation

Next.js wins for Antlers at this moment. The reason is singular and concrete: approximately 3–5 weeks of existing React component work (completed CE pages, the full component library, context providers, API layer) is directly reusable under Next.js but must be rewritten under SvelteKit. That gap is not closed by any advantage SvelteKit offers for this specific product at this specific scale.

SvelteKit is not the wrong framework — it is the wrong framework for a React codebase with an in-flight port and a 40-page migration backlog. If Antlers were being built from scratch today, this would be a closer call.

Operator weights to confirm before sub-cards are filed: