Own site · Platform migration

Next.js migration

I moved this portfolio from static HTML to Next.js and checked that nothing visible changed. Of 212 before-and-after screenshot pairs, 208 were pixel-identical, and the four small differences are measured in the report. Search engines now get clean URLs, real 404s and structured data.

OwnershipMy own site
CompletedOctober 2026
StackNext.js 16 · React 19 · TypeScript
Scope16 routes · 10 case studies
Read case study

The short version

What it is

A design-led portfolio, finished and approved as frozen static HTML, that needed a real platform: server-rendered pages, clean URLs and the metadata search engines read, with no redesign along the way.

What I built

A Next.js 16 App Router site that reuses the original CSS, fonts, images and particle code byte for byte, adds clean routes, per-page metadata, structured data, a sitemap, robots.txt, RSS and real 404s, and shipped with a written parity record.

Why it stands out

Evidence that the move was safe. Every page was captured before and after at four widths and compared pixel by pixel, and the few differences are listed in the report.

212Before-and-after screenshot pairsFive page templates at four widths
208Pixel-identicalThe other four: no colour channel off by more than 5 of 255
176Route and width combinationsZero horizontal overflow
54Reference files fingerprintedThe build fails if one changes

From the migration report and comparison data, 3 October 2026. The moving particle field, cursor and live clocks were masked in both builds; nothing else was.

The brief

A new platform
nobody should notice.

The design was finished and approved as a set of static HTML pages: hand-tuned type, a WebGL particle field, scroll choreography. It looked right, but it was a prototype. All ten case studies shared one page, project.html?p=lambert and its siblings, which arrived as an empty shell for JavaScript to fill in. An unknown project was just a message drawn by a script.

The job was to put it on a production platform without redesigning it. Any visible change had to be deliberate and written down. Everything else had to land exactly where it was, at every width, in every case study.

Same page

Same page.
What Google receives.

A visitor sees the Lambert case study exactly as before. A crawler’s first request for it is where everything changed.

First responseproject.html?p=lambert → /work/lambert+11 −2
− <title>Case study · Alex Jardine</title>+ <title>Lambert Lighting · Alex Jardine</title>+ <meta name="description" content="A specification-ready lighting catalogue: 13 static …">+ <link rel="canonical" href="https://alexjardinedigital.com/work/lambert">+ <meta property="og:title" content="Lambert Lighting · Alex Jardine">+ <script type="application/ld+json">{"@type":"CreativeWork", …}</script>+ <script type="application/ld+json">{"@type":"BreadcrumbList", …}</script>  </head>  <body>− <main id="main"></main>+ <main id="main">+   <h1>Lambert Lighting</h1>+   <p class="lede">Lambert makes architectural lighting in Rhode Island. I rebuilt …</p>+   … the rest of the case study, over 1,200 words+ </main>
Real responses: the frozen prototype file and the live page, trimmed to the lines a crawler reads first. The prototype’s <main> held no words until JavaScript ran.
Lambert Lighting case study hero at 1440 pixels wide, identical in the static HTML reference and the Next.js build.

The Lambert case study at 1440 px, captured at the migration on 3 October 2026 (its copy has been rewritten since). Below the header, the captures from the static prototype and the Next.js build are the same file, so there is nothing to compare by eye. The original stylesheet, fonts and images were reused unchanged.

How it was done

How the move
was checked.

Before any code moved, the approved reference was fingerprinted. Fifty-four source files are hashed, and the production build still refuses to run if one of them changes. If the approved design is edited by accident, the build stops.

The port reused files rather than rewriting them. Search infrastructure was added underneath, and then every page was captured in pairs, from the same browser at the same viewport, and compared. The written record shipped with the code.

The migration sequence

  1. Freeze the referenceHash every approved source file
  2. Port without restylingReuse CSS, fonts, images and scripts as they are
  3. Add what search needsClean URLs, metadata, structured data, sitemap, real 404s
  4. Capture in pairsSame browser and viewport, four widths, moving layers masked
  5. Measure every differencePer-pair pixel deltas saved beside the screenshots
  6. Ship with the recordA report listing each difference and each untested area
Calls I made

Four decisions
during the move.

Reuse the original files

Chose
The original image files and stylesheet, served as they were.
Instead of
Framework image optimisation and a rewritten stylesheet at launch.
Trade-off
Some optimisation waited for a later, separately checked change, in exchange for zero crop or rendering drift on day one.

Report every difference

Chose
Report the four imperfect pairs with their exact deltas.
Instead of
Raising the tolerance until every pair passed.
Trade-off
The headline is 208 of 212 instead of 212, and anyone can check the record.

Mask only the moving layers

Chose
Mask the particle field, cursor and clocks, and nothing else.
Instead of
Masking any region that happened to differ.
Trade-off
Animation cannot be pixel-compared, so it was checked separately by behaviour and frame timing.

Leave an unreproducible issue open

Chose
Record a first-load text size variation in the original that twelve fresh test runs could not reproduce.
Instead of
Shipping a speculative runtime fix.
Trade-off
One open review item in the report instead of new code with unknown side effects.
In-house · Craft CMS to Webflow

Copper CRM,
200+ pages.

I led SEO for Copper CRM’s move of its 200+ page marketing site from Craft CMS to Webflow, working with the in-house team. My part was the redirect and URL map, the content migration and the launch SEO QA.

That was a live site with existing search history, where a migration carries real risk: every old address needs a new home, and every redirect has to work on launch day. Copper’s working files stay with Copper, so the evidence on this page comes from a site I own.

Result

Where it
stands now.

The Next.js build went live in October 2026 and is the site you are reading. Everything added since has been built on it.

Shipped
  • Server-rendered pages at clean /work/ addresses
  • Per-page titles, canonicals and social cards
  • Structured data, sitemap, robots.txt and RSS
  • Real 404s for unknown projects and articles
  • Build checks that fail on reference drift
Not claimed

Safari, physical phones and screen readers were outside this test pass. There are no traffic or ranking figures, because the prototype had no search history to compare against. Moving particles were checked by behaviour, not pixels.

For a client migration

The same method carries over: fingerprint the current site, map every URL, port without redesigning, capture pairs, and launch with a written record of what changed and what was not tested.

All projects

Available now for projects, agency work and contract roles

Have a project or role in mind?

alex@jardinestudio.com

Tell me about the project or the role.

What’s this about?

Toronto, CanadaRemote across Canada and the USIn person across the GTA