All writing
AI EngineeringAugust 14, 20263 min read

A Migration Should Only Migrate: Porting a Vite SPA to Next.js With an AI Assistant

I moved a marketing site from a Vite SPA to the Next.js App Router for SEO, with Claude's help. The port worked. It also rewrote copy nobody asked it to touch.

Henry Iddirisu

Henry Iddirisu

AI product engineer

Two versions of a web page side by side with differences highlighted

The brief was the foundation, not the words.

Routes by Remora, a ride-sharing startup in Accra, had a marketing site built as a Vite single-page app. It looked fine, but search engines saw an empty HTML shell: no server rendering, no per-page metadata, no structured data. For a local transport service that needs to rank for commute searches, that's a real problem.

The job was to move it to the Next.js App Router without changing what users see, except for two approved fixes: the Ghana cedi symbol was wrong, and "Switzerland" was misspelled in a job listing.

I did the port with Claude Code. This is what went right, and what I had to take back.

What the migration added

  • Server rendering for every route, so crawlers get real HTML
  • Per-route metadata: titles, descriptions, and Open Graph tags
  • JSON-LD structured data: Organization, BlogPosting, JobPosting, and FAQPage
  • sitemap.xml and robots.txt generated by the app
  • GA4 wired in properly
  • New /faq and /routes pages targeting local searches for Accra commute corridors

That's the technical foundation the SPA couldn't give. It was the whole point of the project.

What it also changed

Reviewing the port against the live site, I found changes nobody had asked for:

  • a new "Routes" item in the navigation, and different nav link behaviour
  • rewritten copy in the hero, footer, safety and finance sections, and careers page
  • job listings with edited locations and descriptions
  • a blog post reworded
  • the social icons redrawn by hand instead of using the icon library's own
  • app-store badges no longer rendered as plain images

Each change looked plausible on its own, and several arguably read better. None of them were in scope. Marketing copy belongs to the people who own the brand, and a migration PR is the wrong place to change it silently.

How I put it back

I made a separate commit that restored everything visible to the original, and listed each change in the commit message so it could be reviewed line by line:

  • original navigation, including how hash links scroll (or navigate first, then scroll)
  • original hero, footer, safety, finance, and careers copy
  • original job listings, keeping only the approved spelling fix
  • the icon library pinned to the same version the Vite site used, with its own icons
  • plain <img> badges, as before

What remained was exactly the brief: a new foundation, two approved content fixes, and nothing else a visitor would notice.

What I took from it

Write the scope down before the assistant starts. "Change the foundation, keep the output identical, except these two fixes" is a testable instruction. "Migrate to Next.js" isn't.

Review against the original, not against the diff. A diff shows what changed in code. Checking the old and new pages side by side is what showed the copy had drifted.

Pin what you want preserved. Matching the dependency version the old site used is how "same icons" becomes a guarantee instead of a hope.

Undo drift in its own commit. A separate, itemised revert makes it obvious what was restored and why, and keeps the migration commit about the migration.

AI assistants are very good at the mechanical parts of a port, like routes, metadata, and structured data. They're also eager to improve things along the way. Deciding what's in scope is still the engineer's job.

#nextjs#seo#migration#ai-assisted-development
Henry Iddirisu

Henry Iddirisu

AI product engineer · Accra, Ghana · Remote

Keep reading