Skip to content

Scaffold Next.js calculator app as SPA wrapper#851

Open
SakshiKekre wants to merge 10 commits intomainfrom
nextjs-calculator-migration
Open

Scaffold Next.js calculator app as SPA wrapper#851
SakshiKekre wants to merge 10 commits intomainfrom
nextjs-calculator-migration

Conversation

@SakshiKekre
Copy link
Collaborator

@SakshiKekre SakshiKekre commented Mar 23, 2026

Summary

Scaffolds a Next.js app (calculator-app/) that wraps the existing Vite calculator as a client-side SPA, and adds router abstraction infrastructure for incremental route extraction. Follows the official Next.js Vite migration guide.

SPA wrapper

  • [[...slug]] catch-all route imports CalculatorApp with ssr: false
  • import.meta.env polyfilled via webpack DefinePlugin
  • generateStaticParams prerenders the index route
  • Shared CSS/Tailwind imported from app/src/app.css
  • Public assets symlinked from app/public/

Router abstraction (for incremental route extraction)

  • CountryContext — decouples useCurrentCountry() from react-router's useParams
  • NavigationContextuseAppNavigate() with push/replace/back, works in both routers
  • LocationContextuseAppLocation()/useAppPathname() for pathname and search
  • AppLink — drop-in replacement for react-router's Link component
  • All contexts wired into CountryGuard (react-router) and [countryId]/layout.tsx (Next.js)
  • Existing tests updated to provide CountryProvider in test wrappers

CI

  • Calculator typecheck and build steps added to pr.yaml

Dependent PRs and merge strategy

main
  └── #851 (this PR)
        ├── #861 (listing pages)
        ├── #862 (report builder)
        │     └── #869 (report output)
        └── #870 (pathway wrappers)

Merge order: leaves first, then merge up into this PR, then merge to main.

  1. Merge Extract report output route from calculator catch-all to Next.js #869Extract report builder routes to Next.js #862
  2. Merge Extract listing pages from calculator catch-all to Next.js routes #861, Extract report builder routes to Next.js #862, Extract pathway wrappers to Next.js routes #870 → this PR (nextjs-calculator-migration)
  3. Merge this PR → main

This avoids rebasing each PR onto main individually. The PRs are tightly coupled (all part of the same migration, all depend on the same infra), so merging them as a single unit makes sense. Each sub-PR is reviewed individually; this PR is the integration point.

Going live checklist

Before going live

  1. Create Vercel project for calculator-app/ (separate from existing policyengine-calculator)
  2. Configure env vars on new Vercel project: NEXT_PUBLIC_WEBSITE_URL, NEXT_PUBLIC_CALCULATOR_URL, NEXT_PUBLIC_IPAPI_CO_KEY
  3. Set Ignored Build Step so only changes to calculator-app/, app/src/, packages/design-system/ trigger rebuilds
  4. Verify all routes work on the Vercel preview deployment with catch-all removed

Going live

  1. Point app.policyengine.org domain to the new Vercel project

Immediately after going live

  1. Remove the catch-all — delete calculator-app/src/app/[[...slug]]/ (page.tsx + client.tsx). This is dead code once all routes are extracted and removes the entire Vite bundle from the Next.js app
  2. Monitor for any route 404s or broken flows before decommissioning Vite

After Vite decommission (later)

  1. Remove bridge wrappers from CalculatorRouter.tsx (ModifyReportPageRoute, ReportOutputRoute)
  2. Remove react-router-dom dependency
  3. Remove CalculatorRouter.tsx and CalculatorApp.tsx
  4. Remove Vite config, scripts, and app/src/main.calculator.tsx
  5. Consider consolidating app/src/ into calculator-app/src/

Multi-zones (future, independent of Vite decommission)

Once website, model explorer, and API docs are all on Next.js, enable multi-zones to share a single domain (policyengine.org) with seamless client-side navigation. Currently these are separate Vercel projects served via rewrites, causing full page reloads when users click between Model, API, and other apps in the header. Multi-zones would eliminate that. Ref: #755

Dev workflow changes

Before this PR merges

  • cd app && bun run dev:calculator — runs Vite calculator on port 3001

After this PR merges (during migration)

  • cd app && bun run dev:calculator — Vite calculator still works (port 3001)
  • cd calculator-app && bun run dev — Next.js calculator (same port, run one at a time)
  • Both should behave identically for testing

After going live

  • cd calculator-app && bun run dev — primary dev server
  • Vite dev server still available for comparison/debugging until decommissioned

Test plan

  • cd calculator-app && bun run dev — loads full calculator at localhost:3001
  • Navigate between Reports, Simulations, Policies, Populations via sidebar
  • Country switching works (US, UK)
  • cd app && VITE_APP_MODE=calculator npx vite — Vite dev server still works
  • bun run test — all 2950 tests pass
  • bun run lint — no errors
  • bun run typecheck — passes

@vercel
Copy link

vercel bot commented Mar 23, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
policyengine-app-v2 Ready Ready Preview, Comment Mar 25, 2026 9:46pm
policyengine-calculator Ready Ready Preview, Comment Mar 25, 2026 9:46pm
policyengine-website Ready Ready Preview, Comment Mar 25, 2026 9:46pm

Request Review

@SakshiKekre SakshiKekre force-pushed the nextjs-calculator-migration branch from fefeb1c to 56db944 Compare March 23, 2026 15:10
@SakshiKekre SakshiKekre force-pushed the nextjs-calculator-migration branch from 56db944 to c4e2f20 Compare March 23, 2026 15:16
@SakshiKekre SakshiKekre marked this pull request as ready for review March 24, 2026 00:07
@SakshiKekre SakshiKekre changed the title Nextjs calculator migration Scaffold Next.js calculator app as SPA wrapper Mar 24, 2026
@SakshiKekre SakshiKekre force-pushed the nextjs-calculator-migration branch from f92aeb8 to 1b2fe01 Compare March 24, 2026 13:32
@SakshiKekre SakshiKekre force-pushed the nextjs-calculator-migration branch from 1b2fe01 to c9e1d53 Compare March 24, 2026 13:53
@SakshiKekre SakshiKekre force-pushed the nextjs-calculator-migration branch from c9e1d53 to 9372594 Compare March 24, 2026 13:58
SakshiKekre and others added 9 commits March 25, 2026 09:10
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…viders

Swap react-router-dom imports to the abstraction layer (useAppNavigate,
useAppLocation, AppLink) in shared components used by all extracted pages:
StandardLayout, Sidebar, SidebarNavItem, CountrySelector, HeaderLogo, NavItem.

Add CalculatorProviders + MetadataBootstrap in calculator-app for extracted
Next.js routes. Update test utilities with RouterContextBridge.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@SakshiKekre SakshiKekre force-pushed the nextjs-calculator-migration branch from 61f12f5 to dcf2c6d Compare March 25, 2026 16:22
@anth-volk
Copy link
Collaborator

Migration gap: [countryId]/layout.tsx doesn't sync Redux metadata.currentCountry

CountryGuard (the react-router side) dispatches setCurrentCountry(countryId) to Redux on every country change (CountryGuard.tsx:27-31). The Next.js [countryId]/layout.tsx only provides context — it never dispatches to Redux.

This is fine for Phase 1 (the catch-all still routes everything through CountryGuard), but it will become a bug in Phase 2 when routes are extracted to Next.js pages. Two production components read metadata.currentCountry directly from Redux instead of using context:

Suggested fix (can be a follow-up): Migrate both components from useSelector((state) => state.metadata.currentCountry) to useCurrentCountry(). This is a ~6-line diff, requires no new infrastructure, and makes the comment at metadataReducer.ts:118 ("only used internally by useFetchMetadata") accurate.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants