Skip to content

[P2/high] chore(web): bump Next.js 14→15, migrate async request APIs#179

Draft
cipher813 wants to merge 2 commits into
mainfrom
chore/next-15-bump-138
Draft

[P2/high] chore(web): bump Next.js 14→15, migrate async request APIs#179
cipher813 wants to merge 2 commits into
mainfrom
chore/next-15-bump-138

Conversation

@cipher813

Copy link
Copy Markdown
Collaborator

Priority: P2 · Complexity: high

Closes nousergon/metron-ops#138

Bumps the Metron web app (web/) from Next.js 14.2.35 → 15.5.20 and migrates the App Router to Next 15's asynchronous request APIs. React is kept at 18.3.1 to minimize blast radius (Next 15 supports React 18). eslint-config-next bumped to 15.5.20 to match.

Dependabot alerts this should clear

The remaining 5 high-severity alerts originate from next@14.2.35 itself (not transitive — those were handled in #174). Bumping to 15.5.20 moves the app past every published next 14.x advisory range. npm audit --omit=dev reports 0 vulnerabilities after the bump (was 1 high on 14.2.35). Note: the CI/Dependabot API is not readable from this runner's token, so the exact alert numbers should be confirmed on the Security tab once merged.

Migration approach

  1. Ran the official codemod: npx @next/codemod@latest next-async-request-api . (22 files touched).
  2. Manual per-route pass to verify each route and to fix one codemod shortcut it could not migrate safely (previewFromCookies — see below).

Per-route changes (19 pages — params/searchParams now Promise<...>, awaited)

  • app/portfolios/[id]/page.tsx
  • app/portfolios/[id]/watchlist/page.tsx
  • app/portfolios/[id]/today/page.tsx (was sync → now async)
  • app/portfolios/[id]/transactions/page.tsx (was sync → now async)
  • app/portfolios/[id]/tax/page.tsx
  • app/portfolios/[id]/risk/page.tsx
  • app/portfolios/[id]/performance/page.tsx
  • app/portfolios/[id]/holdings/page.tsx
  • app/portfolios/[id]/attribution/page.tsx
  • app/portfolios/[id]/calendar/page.tsx
  • app/portfolios/[id]/alpha-engine/page.tsx
  • app/portfolios/[id]/advisor/page.tsx
  • app/portfolios/[id]/advisor/profile/page.tsx
  • app/portfolios/[id]/macro/page.tsx
  • app/portfolios/[id]/crypto/page.tsx
  • app/portfolios/[id]/research-intel/page.tsx
  • app/portfolios/[id]/settings/page.tsx
  • app/portfolios/[id]/tearsheet/[ticker]/page.tsx
  • app/portfolios/[id]/accounts/[accountId]/page.tsx

cookies() / headers() call sites

  • lib/session.tsawait headers() in getSession, await cookies() in requireTenantId.
  • components/demo-banner.tsxawait cookies().
  • lib/entitlements.tspreviewFromCookies() made async (await cookies()). The codemod had left it sync with an UnsafeUnwrappedCookies cast, which type-checks but would throw / read undefined at runtime under Next 15. Its two callers were updated to await it: app/portfolios/[id]/page.tsx and app/portfolios/[id]/markets-action.ts.
  • Route handlers (app/demo/route.ts, app/api/auth/[...all]/route.ts) use request/response-scoped cookies (NextResponse.cookies) — unaffected by the async change; left unchanged.
  • Client components using useSearchParams() (account-panel.tsx, holdings-view.tsx) are unaffected.

Validation (run in web/, Node 18.20.8 / npm 10.8.2)

Command Result
npx tsc --noEmit ✅ clean (exit 0)
npm run lint ✅ No ESLint warnings or errors
npm run build ✅ succeeds — all 27 routes compiled & page-data collected

Also grepped for any remaining synchronous cookies()/headers()/draftMode() access not preceded by await — none remain (only comments).

Why this is a DRAFT (gate:live-run)

tsc cannot catch runtime params/searchParams breakage, and this repo auto-deploys on merge to main. Full runtime verification of every route against real infra was not possible here:

  • The build was validated on Node 18.20.8; Next 15.5 declares engines: node >=20. CI/prod should run on Node 20+.
  • better-sqlite3 (native) had to be npm rebuild-ed from a prebuilt binary in this sandbox; a clean prod install should compile it normally.
  • npm test (vitest 4 / rolldown) fails to start on Node 18 (node:util.styleText needs Node 20+) — unrelated to this change and not run as a gate.

One command Brian runs to validate (on Node 20+, real env)

cd web && npm ci && npm run build && npm start
# then smoke each portfolio route, especially param/searchParam ones:
#   /portfolios/<id>  /portfolios/<id>/holdings?account_id=<a>&combine=accounts
#   /portfolios/<id>/tax?account_id=<a>  /portfolios/<id>/tearsheet/<ticker>
#   /portfolios/<id>/accounts/<accountId>  /demo (cookie set)  and the tier-simulator (preview cookies)

🤖 Generated with Claude Code

…s#138)

Next 15 makes cookies(), headers(), params, and searchParams asynchronous.
Bump next 14.2.35 -> 15.5.20 and eslint-config-next to match (React kept at
18.3.1). Ran the official `next-async-request-api` codemod, then a manual
per-route pass over all 19 param/searchParam pages plus the cookies()/headers()
call sites.

- 19 app/**/page.tsx: params/searchParams typed as Promise<...> and awaited;
  two former sync redirect pages (today, transactions) converted to async.
- lib/session.ts: await headers() / await cookies().
- components/demo-banner.tsx: await cookies().
- lib/entitlements.ts: previewFromCookies() made async (await cookies());
  callers in page.tsx and markets-action.ts updated to await it. Replaced the
  codemod's UnsafeUnwrappedCookies cast, which would have thrown at runtime.

Validation (web/, Node 18.20.8): `npx tsc --noEmit` clean, `npm run lint`
clean, `npm run build` succeeds (all 27 routes). Closes the remaining
next@14.2.35 Dependabot alerts (npm audit: 0 vulnerabilities post-bump).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@ne-groomer

ne-groomer Bot commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

🤖 [Auto-sweep] Merge conflict due to feature development on main (LiveValuationProvider, SettledRefresher, SessionPanel, etc.) after this branch was created. This PR needs a careful rebase to incorporate both the async API migration (Next.js 15) and main's new feature components. Converted to DRAFT for manual rebase.

@ne-groomer

ne-groomer Bot commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

Merge conflict resolved

This PR's merge conflict in holdings/page.tsx has been resolved by adopting the async params/searchParams migration from this PR (required for Next.js 15 compatibility). Main's sync destructuring has been replaced with the await pattern.

  • Merged commit: Merge remote-tracking branch 'origin/main' into pr-179
  • CI status: ✅ All checks passing (tsc + lint + vitest, pytest + ruff)
  • GitHub merge status may show stale 'CONFLICTING' — the branch is clean and ready; this is a GitHub index lag.

Ready to merge once the status index updates.

@ne-groomer

ne-groomer Bot commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

[GROOM-SWEEP] Conflict detected: Next.js 14→15 refactor + page hierarchy restructuring. 6 files changed; 2 files have merge conflicts (holdings/page.tsx and [id]/page.tsx). The conflict is non-trivial: MAIN moved holdings to be the portfolio landing page (metron-ops-I156 redirect), while HEAD has the full page logic. This requires deliberate 3-way resolution that understands the new routing.

MARKED DRAFT: needs manual resolution or rebase against latest main.

@ne-groomer

ne-groomer Bot commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

Sweep: Complex conflict — needs human judgment

Merge conflict in web/app/portfolios/[id]/page.tsx and web/app/portfolios/[id]/holdings/page.tsx.

The PR branch refactors the portfolios page structure while main recently merged holdings-as-home (PR #199). These are architecturally conflicting changes that need careful reconciliation beyond mechanical merge resolution.

Blocker: Requires manual conflict resolution understanding both the refactoring intent and the holdings-as-home changes.

Converting to DRAFT with gate:live-run label pending resolution.

…-params migration on top of shipped Holdings-is-home (metron-ops-I156) restructuring

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@ne-groomer ne-groomer Bot added gate:decision Draft: needs decision and removed gate:decision Draft: needs decision labels Jul 8, 2026
@cipher813 cipher813 added gate:device Draft: needs device and removed gate:live-run labels Jul 10, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

gate:device Draft: needs device

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant