Nik McFly
← Blog

Published · Updated

How Two AI Agents Migrated a Production Site in 36 Hours

Claude planned and reviewed the migration, Codex implemented it, and I kept the business decisions and production approvals.

#ai#codex#claude#nextjs#operations

We migrated a production music site from a React single-page application to a self-hosted Next.js site in 36 hours.

Codex created 27 commits and 7,803 lines of source code. Claude planned the releases and reviewed the evidence. The finished site had 45 routes with unique metadata in the initial HTML, 1,505 automated checks per release, and a rollback procedure we did not need to use.

I did not write application code. I made six product decisions and approved three production releases.

The useful part of the experiment was the division of responsibility: one AI implemented, another reviewed, and a human owned the decisions that were public or difficult to reverse.

The migration began with a hidden failure

The site looked normal in a browser. Its pages, images, and navigation worked.

The metadata did not.

The application used a library to change each page title, canonical URL, and structured data after React loaded. Under the site’s React 19 setup, those updates stopped reaching the document head. The content rendered, but every route kept the same generic title and no canonical link.

We found the problem by inspecting the rendered page rather than reading the source code:

visible page content: correct
browser tab title: generic
canonical link: missing
structured data: missing

This changed the scope from a small metadata repair to a migration where every important page would arrive with its content and metadata already present in the HTML.

Each participant had one role

Claude was the lead and reviewer. It audited the existing site, wrote the migration plan, divided the work into releases, and checked Codex’s claims against staging and production.

Codex was the implementer. It built the Next.js application, moved the routes, created tests, prepared Docker and Caddy changes, and produced comparison files.

I was the owner. I decided the target architecture, selected final URLs, chose what happened to the old splash page, approved the analytics approach, accepted one content trade-off, and authorized each production change.

Claude did not approve its own code because it did not write the application code. Codex could challenge a review finding, but it had to provide evidence.

The work order defined proof before code

Every release began with a short contract:

  • which routes were in scope;
  • what must not change;
  • what “complete” meant;
  • which tests had to pass;
  • which files had to be saved in the repository;
  • how to return to the previous release.

The first acceptance rule was simple: fetch the page without running browser JavaScript and confirm that the title, canonical URL, structured data, and main content were present.

For a song page, that included the lyrics. For an article, it included the article body. A framework migration did not count if the new site still depended on a second browser request for the information we wanted crawlers to read.

One route list controlled the rest

The old site maintained its routes, sitemap, redirects, and tests in separate files. They had already drifted apart: the sitemap listed pages that no longer existed and missed pages that did.

The new site used one typed route registry. The sitemap, redirect map, public AI index, and acceptance tests were generated from that list.

This removed a class of manual work. Adding or removing a canonical route changed every dependent file in the same build.

Codex also tracked the migration status of every route. Production was blocked until each route had passed the required checks.

We compared the old and new sites directly

For visual review, Codex created three images for each important page:

current production page | migration candidate | pixel difference

The comparison ran at desktop and mobile widths. Large visual changes appeared as bright areas in the difference image.

The project saved 101 comparison images. Not every page needed that level of review, but the method made layout drift difficult to hide behind a sentence saying “looks correct.”

Functional tests checked routes, metadata, redirects, social images, private pages, and error responses. The final production run executed 1,505 assertions.

Both AIs made mistakes

Codex sometimes completed the task but failed to preserve the proof. It wrote reports into a temporary folder, produced empty screenshot sections, and once ran authentication tests against production data.

We fixed the process rather than repeating the request. Evidence had to be committed. Missing screenshots blocked the release. Test data needed a known environment and a cleanup record.

Claude also made incorrect review claims. It read a cached sitemap and reported that a deployed fix was absent. It assumed that a referenced repository existed when only the remote address had been configured. In both cases, Codex challenged the finding with hashes or repository evidence.

The reviewer was independent, not automatically right.

A staging build caused a real incident

Repeated Docker builds filled the server disk. PostgreSQL could not write a checkpoint and the production API briefly returned errors.

The migration stopped. We removed only unused build layers, let the database recover, checked the data and application, and resumed after enough disk space was available.

The permanent fix was a precondition for every later build: check free disk space before pulling or building images. We also added regular cleanup of unused images.

This was the most useful operational rule produced by the project, and it came from a failure rather than the migration plan.

What changed

Check Before After
Routes with unique metadata in initial HTML 0 45
Sitemap URLs that resolved correctly 9 of 15 45 of 45
Automated assertions per release 0 1,505
Saved visual comparisons 0 101
Production releases 0 3
Rollbacks required 0 0

The largest initial JavaScript download fell from about 1.3 MB to 658 KB. A tested mobile song page reached a 1.8-second Largest Contentful Paint in that test environment. Those performance numbers describe this migration, not a promise for another site.

The reusable operating model

For a similar project, I would keep five rules:

  1. Give the implementer a definition of done before implementation starts.
  2. Generate related files from one source of truth where possible.
  3. Require saved evidence for every important claim.
  4. Let the reviewer be challenged with better evidence.
  5. Keep business decisions, external actions, and production approval with a named human.

AI reduced the amount of code I had to write. It did not remove ownership. My main work was deciding what could change, what proof was enough, and when the new version was allowed to become public.