Migrating a jQuery-Era Site to a Modern Framework, Safely

A logistics client's customer portal had been built up over roughly ten years in jQuery and server-rendered PHP templates, with business logic scattered across inline script tags, a handful of jQuery plugins nobody fully understood anymore, and no test coverage. A full rewrite was the obvious answer and also not a realistic one — the business couldn't pause feature development for the six-plus months a clean-slate rewrite would have taken, and a big-bang cutover on a system this load-bearing was too risky.
We used the strangler fig pattern: new features and any page getting significant rework were built fresh in Next.js, while untouched legacy pages stayed on the existing PHP stack, with a reverse proxy (we used a lightweight Express layer, though Nginx works too) routing requests to whichever system owned a given path. This let us ship real progress from week one instead of spending months on a rewrite with nothing shippable until the end.
The hardest part wasn't the new code, it was the shared state between old and new — session/auth needed to work identically on both sides, since a user shouldn't get logged out moving between a legacy page and a migrated one. We centralized auth on a shared JWT issued by the PHP backend, validated by both the legacy session middleware and the new Next.js API routes, which meant no double-login system and no separate user session to keep in sync.
We prioritized migration order by traffic and business risk, not by code quality — the pages developers most wanted to burn down weren't necessarily the ones costing the business the most in bugs or lost conversions. The shipment tracking page, the highest-traffic page on the whole portal and also the one throwing the most console errors, went first; a rarely-used internal reporting page that was arguably worse code went last, migrated almost a year later.
For each migrated page, we ran the old and new versions side by side behind a flag for two weeks, comparing key user actions and error rates before fully cutting over, which caught several subtle behavior differences (a date formatting discrepancy, a filter that silently excluded a status the old page included) that would have been real regressions if we'd cut over blind.
Eighteen months in, roughly 70% of the portal's pages by traffic are migrated, the legacy PHP codebase hasn't grown a single new feature since the migration started, and — critically — the business never had a multi-month feature freeze or a risky cutover weekend.

