Lavine Web & AI Solutions Logo
Deep Dive
April 2026 · 11 min read

The launch that went wrong: what I learned rebuilding a legacy platform on Next.js

Every developer has a war story. This is mine.

Developer Life

Last year I rebuilt a large UK online qualifications platform — a government-funded training provider working with colleges and employers across England. The previous site was a bespoke Laravel system, built and maintained by a developer in the Netherlands, with deep Salesforce integration and absolutely no CMS, no SEO optimisation, and no cookie consent to speak of.

My job was to replace it with something modern, maintainable, and built for growth. Next.js for the frontend, Strapi 5 as the CMS, Salesforce sync retained for courses, enrolments, and customers.

The build went well. The launch did not.

The project

The scope was significant. A full platform rebuild — not a redesign, a complete architectural change. Out went the Laravel system with its hardcoded content and zero editorial control. In came a proper headless CMS with a Salesforce integration that synced course listings, student enrolments, and customer records in both directions.

The AI workflow at this point was similar to what I'd used on the Skipper & Skipper build — using it as a coding assistant and brainstorming partner rather than an agent. "How do I get from A to B?" and "this code isn't doing what I expect, can you spot why?" rather than directing it to build entire features. Useful, not transformative.

The Salesforce integration was the most complex part. The previous developer had built direct connections between the Laravel system and Salesforce over years. Replicating and modernising those connections in Strapi 5 — which was still relatively new at the time — required careful work and added time I hadn't fully accounted for.

Which brings me to the first lesson.

Lesson one: no fat in the schedule is a gamble you will eventually lose

The site was due to launch on February 1st 2025. It launched on February 28th.

That's on me. I mistimed the project by about four weeks. I didn't leave myself any buffer for the unexpected — and in a project with a live Salesforce integration, a brand new CMS version, and a legacy codebase to understand and replicate, unexpected things were basically guaranteed.

I'm not going to dress it up. I got the estimate wrong. The client wasn't happy, the agency I was working through wasn't happy, and I spent a very uncomfortable February working 12-hour days to close the gap.

The lesson isn't complicated: always build in contingency, especially on projects where you're inheriting someone else's architecture. The things you don't know will always take longer than the things you do.

Lesson two: migrating from a bad old site is not the same as launching a new one

Here's where it gets interesting.

The previous site was, technically speaking, a mess. No SEO optimisation, no structured data, no cookie consent. The kind of site that a developer had built to work, without any particular thought for how Google would interpret it.

And yet — Google had been indexing it for years. It had rankings. It had traffic. Not because it deserved them, but because it had been there long enough to accumulate domain trust.

When we launched the new site, something happened that I've since learned is a recognised — if unpredictable — phenomenon: Google's algorithm treated the migration with deep suspicion. A major platform change, a new CMS, suddenly a cookie consent layer that crawlers hadn't encountered before, different URL structures, different rendering behaviour. The traffic didn't just dip. It bottomed out.

The client was not pleased. The agency was not pleased. And in the absence of a clear technical explanation, the blame landed on the most convenient target: me.

Lesson three: Google will break your launch and then shrug

Over the following weeks it became clear that this wasn't a build problem. The new site was faster, better structured, properly optimised, and technically superior to what it replaced in every measurable way. But Google's algorithm had flagged the domain during the migration window and essentially put it in a holding pattern while it re-evaluated everything.

This is, to put it diplomatically, a known issue that Google is not particularly forthcoming about. They eventually acknowledged — informally, carefully, in a way that their legal team would approve of — that the algorithm had caused the traffic drop. They did not admit liability. They did not offer compensation. They did not send flowers.

The client, to their credit, eventually accepted this. But not before several weeks of difficult conversations in which I was defending a build that I knew was sound against an algorithm decision that I had no control over and couldn't fully explain in real time.

Lesson four: the GTM problem nobody had documented

While the organic traffic was bottoming out, a second, separate crisis was quietly burning through the client's Google Ads budget.

The previous Laravel site had Google Tag Manager set up for conversion tracking. The new Next.js site inherited that GTM configuration. On the surface, everything looked connected. In practice, the conversions had stopped firing — and the Ads algorithm, receiving no conversion signals, had entered a doom loop.

No conversions → algorithm assumes the traffic isn't working → bids higher to find converting traffic → more spend, fewer results → budget disappearing with nothing to show for it. A significant Google Ads budget was being poured down the drain in real time.

The root cause was a fundamental difference in how GTM behaves on a traditional server-rendered site versus a Next.js application. On Laravel, every page is a full browser load — GTM fires reliably on each one. Next.js uses client-side navigation between pages, which means subsequent page views don't trigger traditional GTM events unless you've explicitly implemented route change listeners. Nobody had. The GTM setup had been built for a different rendering architecture entirely and nobody — including Google — had documented what needed to change.

I raised it with Google. Their engineers were, to put it generously, inconsistent. I received contradictory setup guidance across multiple conversations. Different engineers recommended different configurations. Some of the advice actively made things worse. This wasn't a case of me missing something obvious — this was a genuinely undocumented edge case that Google's own support team hadn't encountered cleanly before, at least not for this specific combination of Next.js, GTM, and enhanced conversions.

The eventual solution was a combination of three things:

  1. A hard page load on the checkout. Rather than letting Next.js handle the checkout navigation client-side, we forced a full browser refresh at that specific step. This ensured GTM fired exactly as it would on a traditional site at the critical conversion moment — ugly as an architectural decision, but pragmatic and effective.
  2. GCLID persistence on page load. Google Click IDs arrive in the URL when a user lands from an ad. On a Next.js SPA, that parameter can get lost during client-side navigation before the user reaches checkout. We captured and stored the GCLID in a cookie on the initial page load, so it was still available for attribution when the conversion fired, regardless of how many client-side navigations had happened in between.
  3. Enhanced conversions. Google's server-side conversion measurement layer, which supplements client-side GTM firing with hashed first-party data. This provided a fallback measurement layer that could fill gaps when client-side tracking was unreliable — which, during this period, it frequently was.

The combination worked. Conversions started flowing through correctly, the Ads algorithm had data to work with again, and CPCs began normalising. But the budget burned during the weeks it took to diagnose and fix this didn't come back.

If you're migrating any site with Google Ads conversion tracking to Next.js: test your GTM implementation exhaustively before you launch, not after. Force a hard page load at the conversion point, implement GCLID persistence on landing, and enable enhanced conversions as a baseline. Don't assume that because GTM is installed and the tags look connected, it's actually working. It probably isn't.

This combination isn't documented anywhere by Google as a recommended Next.js setup. It should be. I'm writing it here so the next developer in this situation has something to find.

The holiday from hell

The Google issue wasn't resolved until the end of April. I had a holiday booked with my dad in America from March 26th to April 10th — planned months in advance, the kind of trip you don't cancel.

I went. I was also on call the entire time.

There's a particular kind of stress that comes from sitting in a diner in some American city, trying to be present with your family, while your laptop is open on the table and you're monitoring a client's analytics dashboard and fielding calls from an agency wanting to know why their client's organic traffic is still down.

I don't recommend it.

If there's a lesson in that specific experience, it's this: production issues don't respect your personal life, but you have to try and structure your projects so that a crisis doesn't automatically become your problem to solve alone at 2am in a different time zone. Better documentation, better handover procedures, better communication upfront about what happens when things go wrong — these aren't just nice to haves, they're things you put in place precisely for the moments when everything goes sideways at the worst possible time.

The resolution

By the end of April, both crises had been resolved. The organic traffic had stabilised and started recovering — the algorithm had finished its re-evaluation of the migrated domain and rankings began returning. The GTM conversion tracking was correctly configured, the Ads algorithm had the data it needed, and CPCs had normalised.

The build was vindicated on both counts. The client relationship, while strained, survived.

The site is performing well now. The Salesforce integration works as designed. The CMS gives the client editorial control they never had before. The cookie consent implementation is correctly configured. The conversion tracking is firing cleanly.

The £5,000 in lost revenue I absorbed during the crisis period didn't come back. That's the real cost of a launch that goes wrong, and it's worth naming: it's not just stress and difficult conversations, it's money.

What I'd do differently

On timing: I'd build in a minimum 20% contingency on any project involving legacy system integration. The things you don't know about someone else's architecture will always surprise you. Budget for surprises.

On migrations: I'd now insist on a soft launch period for any significant platform migration — keeping the old site live in parallel while Google re-indexes the new one, rather than a hard cutover. This is standard practice for large sites and I should have pushed for it harder.

On cookie consent: I'd implement and test cookie consent handling with crawlers explicitly in mind before any migration. It's a commonly overlooked factor in post-launch ranking drops and it bit us here.

On client communication: When a launch goes wrong, the absence of a clear explanation is as damaging as the problem itself. I should have been faster to present a credible technical hypothesis — even an uncertain one — rather than waiting until I was sure. Clients and agencies will fill the information vacuum with blame if you leave it open.

On GTM and Next.js: Never assume a GTM configuration from a server-rendered site will work on Next.js. Before launch: force a hard page load at the conversion point, implement GCLID persistence on landing, and enable enhanced conversions. Test every conversion path manually. If you're running Google Ads, a broken conversion tracking setup will burn budget silently and you won't know until the damage is done.

On holidays: I'm still working on this one.

The honest summary: I mistimed a project, Google had a crisis with the migration, an undocumented GTM/Next.js incompatibility burned through the Ads budget, and I spent two weeks fielding the fallout from a different continent. It cost me money, it cost me sleep, and it cost me a holiday I'd been looking forward to.

It also taught me more about the realities of platform migration, Google's algorithm behaviour, and project management than any smooth launch ever could.

War stories are how you get better. This one's mine.


Steve Lavine is a full-stack developer and founder of Lavine Web & AI Solutions. He builds things that last — and occasionally learns expensive lessons about the ones that don't. lavine.dev