Modern teams want faster delivery, better SEO, and a stack that scales. This case study shows how I migrated a production PHP app to Next.js (App Router) + Node.js in just three weeksβwithout breaking URLs, SEO, or user sessions.
Project Goals
- Zero/near-zero downtime during cutover
- URL parity (keep existing slugs and query params) to preserve SEO
- Same or better performance (LCP/TTFB)
- Modern DX (TypeScript, testing, CI/CD)
- Gradual migration using a strangler-fig pattern (old + new side-by-side)
Architecture at a Glance
Key idea: route-by-route replacement. Nginx (or middleware) sends traffic to Next.js for migrated paths and to PHP for the rest.
Week-by-Week Plan (3 Weeks)
Week 1 β Discovery, Foundations, and Proxy
Day 1β2: Audit & map
- Inventory URLs, templates, forms, and API endpoints.
- Identify critical flows (login, checkout/contact, search, top landing pages).
- Export MySQL schema and sample data.
- Decide which URLs migrate first (80/20).
Day 3: Environment & repo
- Create monorepo (or separate repos): apps/web (Next.js), infra, and scripts.
- Set up TypeScript, ESLint, Prettier, Husky, and CI (GitHub Actions).
- Configure Prisma against existing MySQL:
- Add Redis (ElastiCache or Docker) for hot caching.
Day 4: Routing & SEO parity
- Implement App Router structure mirroring legacy slugs:
- Use generateMetadata for titles, OpenGraph, and canonicals:
Day 5: Strangler proxy
- Put Nginx in front; send migrated routes to Next.js, others to PHP:
- For Vercel/Edge, you can do it in middleware while PHP remains under /legacy:
Deliverables end of Week 1:
Working Next.js shell with parity routing for the first set of pages, Nginx proxy in front, Prisma connected, Redis ready.
Week 2 β Feature Parity & Data Flows
Day 6β7: Data layer & caching
- Wrap legacy queries with Prisma services:
- Use revalidate = 60 for ISR-style freshness where appropriate.
Day 8β9: Auth migration
- Legacy PHP session β JWT session bridge:
- Create a compat endpoint on PHP that exchanges legacy PHPSESSID for a short-lived JWT.
- Next.js middleware verifies JWT for protected routes.
Day 10: Forms & mutations
- Replace PHP form posts with Next.js Server Actions or API routes:
Day 11: Media & assets
- Migrate /uploads to S3 + CloudFront.
- Write a script to copy and rewrite URLs in content:
Day 12β13: Critical flows
- Rebuild homepage, top landing pages, product/blog detail, and search.
- Snapshot test legacy vs new HTML where SEO matters.
- Add Playwright E2E for top 5 flows.
Deliverables end of Week 2:
Key pages & flows live on Next.js behind the same domain, auth bridged, forms functional, media served via CDN, tests passing.
Week 3 β Perf, SEO, Rollout & Cutover
Day 14β15: Performance
- Audit with Lighthouse and Web Vitals.
- Use next/image (remotePatterns) and proper sizes.
- Add HTTP caching for static/edge responses.
- Preload critical fonts; remove render-blocking assets.
Day 16: SEO & redirects
- Maintain 1:1 URL parity; add 301 for any changes:
- Add sitemap + robots via app routes:
Day 17: Observability
- Sentry for errors, Uptime for monitoring, structured JSON logs.
- Add request-id headers in Nginx and propagate to logs.
Day 18β19: Staged rollout
- Blue-green: stand up Next.js as blue, keep PHP as green.
- Shift 10% traffic to blue via Nginx map or ALB weighted target groups.
- Watch errors/metrics, then 50%, then 100%.
Day 20β21: Decommission & cleanup
- Remove unused PHP routes; keep a /legacy admin-only path for a week.
- Final content resync; lock writes on legacy.
- Hand off docs & runbook.
Deliverables end of Week 3:
Next.js fully serves the site, SEO intact, improved performance, monitoring live; legacy safely retired.
Key Implementation Details
1) Data Access with Prisma (keeping legacy schema)
- Use db pull to respect existing tables.
- Avoid destructive migrations early; add new tables in a shadow schema if needed.
2) Re-using PHP logic safely
If an edge case is encoded in PHP, wrap it behind a small internal endpoint and call it from Next.js initially. Replace with TypeScript later.
3) Caching strategy
- Edge or route-level cache for read-heavy pages.
- Redis for DB results and expensive aggregations (TTL 60β300s).
- Invalidate keys on writes (Prisma middleware or service layer).
4) Accessibility & UX
- Server components β minimal JS.
- Accessible forms, focus management, and skeleton/streaming for perceived speed.
Testing Matrix
- Unit: helpers, services (Vitest/Jest).
- Integration: API routes & Server Actions with test DB.
- E2E: Playwright for top paths & form submissions.
- Contract tests: ensure JSON shapes match any external consumers.
Deployment Options
- Vercel: simplest for Next.js; connect to existing DB/Redis; keep Nginx (or middleware) for legacy routing.
- EC2 + Nginx + PM2: control and proximity to legacy infra.
- Docker: reproducible builds; push to ECR; run via ASG.
Example GitHub Action (Vercel + Prisma deploy):
Risks & How I Mitigated Them
- SEO loss β URL parity, canonical tags, 301s, sitemap parity.
- Auth mismatch β JWT bridge; dual-write sessions until cutover.
- Hidden business rules in PHP β temporary service wrapper; replace later.
- Timeline β prioritize top 20% of traffic first; cut scope that doesnβt move KPIs.
Results (typical outcomes)
- TTFB down 30β50% on content pages
- LCP improved via server components + optimized images
- Deploy times from hours β minutes via CI/CD
- Dev velocity up thanks to TypeScript, component reuse, and testing
(These improvements are representative of similar migrations; exact numbers vary by project.)
Migration Checklist (copy/paste)
- URL inventory & priority map
- Prisma introspection; Redis set up
- Nginx or middleware proxy for strangler pattern
- App Router routes & metadata parity
- Auth bridge (PHP session β JWT)
- Top pages migrated; forms via Server Actions
- Assets to S3 + CDN; rewrite URLs
- Web Vitals targets met; tests passing
- Staged rollout (10% β 50% β 100%), monitors green
- Legacy decommission plan

