You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Track B of #744. Migrate the framework Next.js 15 App Router → TanStack Start (SSR), deployed on Cloudflare Workers. Keep the Supabase + Neynar data layer (data-layer evaluation is separate: #742).
🔄 Approach update — in-place migration, React 19 first
The migration runs IN-PLACE in this repo — there is no separate app/folder. An earlier exploration used a parallel web/ directory to sidestep React 18-vs-19; that's been dropped. A separate tree would force duplicating all of src/ (components, the 6 stores, the 25 pages) in Phases 2–3 — the opposite of the goal.
Prerequisite — React 19 (PR #759, in flight): Next 15 supports React 19, so upgrading the whole repo to React 19 lets Next and TanStack Start share one dependency tree (no second app needed). Validated clean: tsc --noEmit0 errors, next build✓ (all 25 pages + 31 routes), 128 tests pass, single deduped react@19.2.7 copy (the @mod-protocol/react/react-editor^18 peer pins are handled via pnpm overrides). No codemods needed — the codebase has none of the React-19 breaking patterns (ReactDOM.render/findDOMNode/string-refs/useFormState/defaultProps/propTypes/argless useRef).
Build the Phase 1 foundation in-place: TanStack vite.config.ts + wrangler.jsonc + src/routes/ + providers/auth/next-font→@fontsource+@font-face/next-image primitives at the repo root, reusing the existing src/globals.css / fonts / tailwind.config.js (no copies). Next keeps running alongside until Phase 4.
Phases 2–4 as below.
The throwaway web/-folder Phase-1 spike lives on branch feat/tanstack-start-phase1 (reference only — its auth-cookie wiring, font replacement, and next/image primitive port directly to the in-place layout). It will not be merged.
Why (drivers)
Cloudflare hosting for high reproducibility / forkability(primary subgoal).
herocast is effectively a client SPA wearing App Router: only 1 of 25 pages is a real async server component (app/oauth/consent/page.tsx); the rest are 'use client'. No middleware.ts, no server-side service-role Supabase key, no CPU-heavy or edge routes, and 31 API routes that are nearly all thin I/O proxies to Neynar/Supabase/fetch. Migration cost is concentrated in tooling/config + mechanical volume, not architecture.
The 3 unknowns the spike must resolve
@neynar/nodejs-sdk under nodejs_compat on Workers — 13 routes depend on it (highest blast radius). Fallback: inline Neynar REST over native fetch (cheap per route, but expands scope).
unstable_cache replacement — 12 routes; no Start equivalent → Cloudflare Cache API / KV (or client-side React Query staleness).
Supabase session cookie in a Start server fn — the @supabase/ssr get/set/remove adapter; auth/callbackexchangeCodeForSession + redirect is the make-or-break login path.
Hard-but-known work (estimable, low-novelty)
unstable_cache ×12 · Sentry@sentry/nextjs → @sentry/cloudflare (rewrite instrumentation.ts + sentry.server/edge.config.ts + webpack plugin → @sentry/vite-plugin; reuse existing scrubbers) · next/font (9 faces: 3 Google + 6-weight local Satoshi — must preserve the --font-sans/display/mono CSS vars feeding Tailwind) · next/image ×5 (no Start optimizer; CF Images / unpic / plain <img>) · embeds/metadata WASMfs.readFileSync (HIGH — bundle .wasm as Workers module/binding; outputFileTracingIncludes is Vercel-only) · ~100 mechanical next/server + next/navigation rewrites (codemod-able, except 18 useSearchParams → typed useSearch).
Ports for free: PostHog (client-only), the @faker-js/faker webpack stub → Vite resolve.alias, next/router (already dead, 0 importers).
API-route risk (highlights, 31 routes)
HIGH:embeds/metadata (WASM file-read).
MED (blast radius): 13 routes on @neynar/nodejs-sdk; 12 routes on unstable_cache; auth/callback + oauth/decision cookie handling.
LOW:onchain/* (hex string-slicing only, no ABI decode), dms/*, miniapp/manifest, hypersnap/[...path] proxy, signerRequest (axios proxy). No route uses a service-role key; the one ed25519/EIP-712 signing path runs client-side (warpcastLogin), so no Workers CPU-time concern.
Port app/api/feeds/trending (Neynar SDK + unstable_cache) + a getUser() cookie server-fn + one SSR route. Settles all 3 unknowns in one low-stakes slice. Excluded from the spike: embeds/metadata WASM, next/font, next/image, wallet/auth-kit (known-hard, shouldn't gate go/no-go).
Go/no-go risk matrix
#
Risk
Effort
Mitigation
1
@neynar/nodejs-sdk on Workers (13 routes)
M
probe in spike; fallback = inline Neynar REST via fetch
2
unstable_cache removal (12 routes)
M
CF Cache API / KV, or client-side React Query
3
embeds/metadata WASM file-read
M–L
import .wasm as Workers module; or move trek parse client-side / drop route
validate in spike; Supabase has framework-agnostic SSR cookie patterns
8
next/navigation volume (52 files)
M
codemod the mechanical ~80%; hand-port 18 useSearchParams
Not risks (de-riskers): no middleware.ts, no server-side service-role secret, no CPU-bound/edge routes, one async server-component page, PostHog client-only, faker stub portable, next/router dead.
Actual wrangler deploy needs Cloudflare credentials (not available in the planning sandbox). The top unknown — Neynar SDK on workerd — may be probeable locally via wrangler dev without a CF account.
Track B of #744. Migrate the framework Next.js 15 App Router → TanStack Start (SSR), deployed on Cloudflare Workers. Keep the Supabase + Neynar data layer (data-layer evaluation is separate: #742).
🔄 Approach update — in-place migration, React 19 first
The migration runs IN-PLACE in this repo — there is no separate app/folder. An earlier exploration used a parallel
web/directory to sidestep React 18-vs-19; that's been dropped. A separate tree would force duplicating all ofsrc/(components, the 6 stores, the 25 pages) in Phases 2–3 — the opposite of the goal.Prerequisite — React 19 (PR #759, in flight): Next 15 supports React 19, so upgrading the whole repo to React 19 lets Next and TanStack Start share one dependency tree (no second app needed). Validated clean:
tsc --noEmit0 errors,next build✓ (all 25 pages + 31 routes), 128 tests pass, single dedupedreact@19.2.7copy (the@mod-protocol/react/react-editor^18peer pins are handled via pnpmoverrides). No codemods needed — the codebase has none of the React-19 breaking patterns (ReactDOM.render/findDOMNode/string-refs/useFormState/defaultProps/propTypes/arglessuseRef).Sequencing:
vite.config.ts+wrangler.jsonc+src/routes/+ providers/auth/next-font→@fontsource+@font-face/next-imageprimitives at the repo root, reusing the existingsrc/globals.css/ fonts /tailwind.config.js(no copies). Next keeps running alongside until Phase 4.Why (drivers)
preload="intent"— absorbs [Responsiveness] Prefetch-on-intent for profile/cast navigation #737 and the prefetch half of [Responsiveness] Instant feed switching (keepPreviousData + adjacent prefetch) #736.Forkability bar (scope)
wrangler deploy+ a secrets template (.dev.vars/wrangler.tomlexample) stands up a fork.Success criteria
wrangler deploy+ secrets → running.Phases
vite.config.ts/wrangler.jsonc, root route + app shell, auth, providers (TanStack Query, themes); replacenext/font+next/imagereusing the existingsrc/globals.css/ fonts /tailwind.config.js; secrets template. (See thefeat/tanstack-start-phase1reference branch.)preload="intent"(absorbs [Responsiveness] Prefetch-on-intent for profile/cast navigation #737 + [Responsiveness] Instant feed switching (keepPreviousData + adjacent prefetch) #736 prefetch half).next/*sites + Vercel config; document the forkable deploy; decommission Next.Phase 0 discovery — feasibility audit ✅ (read-only spike, complete)
Verdict: GO
herocast is effectively a client SPA wearing App Router: only 1 of 25 pages is a real async server component (
app/oauth/consent/page.tsx); the rest are'use client'. Nomiddleware.ts, no server-side service-role Supabase key, no CPU-heavy or edge routes, and 31 API routes that are nearly all thin I/O proxies to Neynar/Supabase/fetch. Migration cost is concentrated in tooling/config + mechanical volume, not architecture.The 3 unknowns the spike must resolve
@neynar/nodejs-sdkundernodejs_compaton Workers — 13 routes depend on it (highest blast radius). Fallback: inline Neynar REST over nativefetch(cheap per route, but expands scope).unstable_cachereplacement — 12 routes; no Start equivalent → Cloudflare Cache API / KV (or client-side React Query staleness).@supabase/ssrget/set/remove adapter;auth/callbackexchangeCodeForSession+ redirect is the make-or-break login path.Hard-but-known work (estimable, low-novelty)
unstable_cache×12 · Sentry@sentry/nextjs→@sentry/cloudflare(rewriteinstrumentation.ts+sentry.server/edge.config.ts+ webpack plugin →@sentry/vite-plugin; reuse existing scrubbers) ·next/font(9 faces: 3 Google + 6-weight local Satoshi — must preserve the--font-sans/display/monoCSS vars feeding Tailwind) ·next/image×5 (no Start optimizer; CF Images /unpic/ plain<img>) ·embeds/metadataWASMfs.readFileSync(HIGH — bundle.wasmas Workers module/binding;outputFileTracingIncludesis Vercel-only) · ~100 mechanicalnext/server+next/navigationrewrites (codemod-able, except 18useSearchParams→ typeduseSearch).@faker-js/fakerwebpack stub → Viteresolve.alias,next/router(already dead, 0 importers).API-route risk (highlights, 31 routes)
embeds/metadata(WASM file-read).@neynar/nodejs-sdk; 12 routes onunstable_cache;auth/callback+oauth/decisioncookie handling.onchain/*(hex string-slicing only, no ABI decode),dms/*,miniapp/manifest,hypersnap/[...path]proxy,signerRequest(axios proxy). No route uses a service-role key; the one ed25519/EIP-712 signing path runs client-side (warpcastLogin), so no Workers CPU-time concern.next/*surface (~106 files)next/server×34 (→ WebRequest/Response, mechanical) ·next/navigation×52 (→ Router hooks;useSearchParams→typeduseSearchis the only semantic shift) ·next/link×26 ·next/image×5 (HARD) ·next/font×1 file/9 faces (HARD) ·next/dynamic×4 (ssr:falsesemantics differ) ·next/cache×12 (HARD) ·next/headers×3 (auth cookies).Thinnest viable spike (the Phase 0 proof)
Port
app/api/feeds/trending(Neynar SDK +unstable_cache) + agetUser()cookie server-fn + one SSR route. Settles all 3 unknowns in one low-stakes slice. Excluded from the spike:embeds/metadataWASM,next/font,next/image, wallet/auth-kit (known-hard, shouldn't gate go/no-go).Go/no-go risk matrix
@neynar/nodejs-sdkon Workers (13 routes)fetchunstable_cacheremoval (12 routes)embeds/metadataWASM file-read.wasmas Workers module; or move trek parse client-side / drop route@sentry/cloudflare+@sentry/vite-plugin; reuse scrubbersnext/font(9 faces)@fontsource+ manual@font-face; keep CSS-var namesnext/image(5) + optimizer lossunpic/<img>next/navigationvolume (52 files)useSearchParamsNot risks (de-riskers): no
middleware.ts, no server-side service-role secret, no CPU-bound/edge routes, one async server-component page, PostHog client-only, faker stub portable,next/routerdead.Key files (spike + decision)
app/api/feeds/trending/route.ts,app/api/embeds/metadata/route.ts,src/common/helpers/supabase/route.ts,app/api/auth/callback/route.ts,app/oauth/consent/page.tsx,app/layout.tsx,app/providers.tsx,next.config.mjs,src/instrumentation.ts,sentry.server.config.ts,sentry.edge.config.ts,src/lib/faker-stub.ts.Environment note
Actual
wrangler deployneeds Cloudflare credentials (not available in the planning sandbox). The top unknown — Neynar SDK on workerd — may be probeable locally viawrangler devwithout a CF account.Relationships