Lessons From a High-Pressure Platform Migration
Note: I’ve generalized some details to protect confidentiality. The point of this post is to share the engineering and leadership lessons, not to single out any specific company or partner.
The Setup
This was not one of those clean modernization projects where everybody aligns on the plan, the team structure makes sense, and you get to build carefully.
It happened in the middle of a broader restructuring. Teams were shifting, priorities were moving, and there was still pressure to modernize a very visible web platform against a fixed date.
One part of the organization already had a more mature web setup. Another part needed a significant refresh. That made the migration feel urgent, which usually leads to the same instinct: centralize as much as possible, move fast, and sort out the details later.
A delivery partner was already in place to support strategy, web production, and part of the engineering work. That may sound efficient, but it left out an important reality: my team still owned the backend platform and the critical integrations that made the site actually work in production, including systems tied to marketing automation, analytics and tag management, and consent/compliance.
There was also a leadership transition near the start of the effort. By that point, the date was already committed and the delivery model already had momentum. So even though the risks were visible, there was not much room to stop and redraw the boundaries the way I think we should have.
When my team got involved, the plan looked reasonable enough from a distance.
Once the work started, it became clear that “reasonable on paper” and “healthy in practice” were two different things.
A few months in, we had something that worked. But too much of the frontend arrived late enough that the final review cycle became compressed. From that point on, the question was less “is this the right implementation?” and more “is this safe enough to ship without missing the date?”
Where It Started to Go Wrong
During discovery, the partner evaluated options for replacing a legacy CMS and recommended a modern stack built around a headless CMS, a front-end framework, and managed hosting.
That choice itself was not the issue.
The issue was that my team already had real production experience with that approach, and the partner did not. So the people building major parts of the implementation were still learning their way around the stack, while the team that knew it best did not have enough authority to set hard rules early.
That is where the trouble started.
You can absolutely use outside help on a migration. I am not against that. But if the partner is still getting comfortable with the stack and your internal team is the one that will inherit the system, you need very clear technical guardrails from day one.
We did not have enough of that.
The Scope Looked Centralized. It Wasn’t.
As the deadline pressure increased, the partner pushed to own more of the work: content modeling, design implementation, and engineering delivery.
The reasoning was familiar: fewer handoffs, fewer delays, faster execution.
I understand the logic. The problem is that it was only partly true.
My team still had to build and support the backend platform and all the integrations connecting the site to the rest of the business. So while the project may have looked centralized from the outside, it was actually split across teams with very uneven control over architecture, code quality, and production readiness.
We also recommended reusing a proven architecture from a mature implementation elsewhere in the organization. That would have lowered risk and shortened the path to something stable. Instead, the push was to rebuild from scratch.
I have seen this before. “From scratch” always sounds cleaner in planning conversations than it feels six months later when your team is maintaining it.
My team did have some input into the architecture, but not early enough and not strongly enough to put real constraints in place. Under deadline pressure, soft influence only gets you so far.
What the Partner Did Well
To be fair, they were not weak across the board.
The strategy work was good. The design direction was fine. They moved quickly and they were responsive.
That is part of what made this situation tricky. It was not a story of obvious failure. It was a story of quality problems showing up in the places that matter most after launch.
The engineering and QA side were where things started to break down.
What Showed Up in the Code
The pull requests had patterns that were hard to ignore:
duplicated components where there should have been reusable building blocks
business logic sitting inside UI components
code that handled the happy path but not much else
very little meaningful automated testing
inconsistent implementation patterns depending on who touched the work
None of that made the demos look terrible.
That is usually how this goes.
A rushed migration can look fine in review meetings and still leave behind a codebase your team does not really want to own.
That was the issue here. The code was getting us toward launch, but not toward a healthy platform.
When the Review Process Became the Real Project
By then, the biggest problem was no longer just code quality. It was timing.
My team delivered the backend platform work and the critical integrations on time. The problem was that we received major frontend deliverables from the partner later than we should have. That compressed the review window badly and changed the kind of decisions we were able to make.
Instead of reviewing code with enough time to push for better structure, cleaner abstractions, or stronger test coverage, we were often reviewing with the date already breathing down our necks.
That shifts the conversation fast.
At that point, review is no longer just about quality. It becomes triage.
We were asking:
Is this safe enough to ship?
Is there anything here that will create immediate production risk?
Can we live with this for now and fix it later?
That is a very different bar than the one you want on a healthy platform migration.
The partner felt pressure to keep moving. My engineers felt like they were doing compressed cleanup under deadline instead of proper engineering review. And once you are in that mode, the bar does come down, even if nobody wants to say it that directly.
We made the date.
But part of how we made it was by accepting code that we would not normally have accepted if the frontend had arrived on time and the review cycle had been healthy.
That is where a lot of the later tech debt came from.
What Actually Helped
One of the best moves we made was to stop treating every code quality issue like it belonged in the same bucket.
When the deadline is fixed, you need a shared understanding of what absolutely cannot ship and what you are reluctantly willing to carry for a while.
So we wrote it down.
Non-negotiable
These blocked the PR:
security issues
data integrity issues
accessibility regressions
major performance risks
Not good, but acceptable for now
These were flagged, documented, and sometimes shipped:
duplication
weak abstractions
incomplete tests outside critical paths
code that was awkward but still readable
That helped because it took some emotion out of the review process. People were no longer arguing in the abstract about whether code was “good.” They were deciding whether it crossed a line we had already agreed on.
We also tagged consciously accepted debt as a red flag and logged it into a cleanup backlog with context. That part mattered. Debt is much easier to deal with when it is visible and named instead of buried in commit history waiting to surprise somebody later.
The Real Lesson
The easiest version of this story is that a partner optimized for delivery and my team had to deal with the aftermath.
There is some truth in that, but it is too simple.
The backend work was not the issue. My team delivered the platform layer and integrations on time. The real damage happened when late frontend delivery collapsed the review window. Once that happened, code review stopped being a quality gate and became a risk filter. That is how standards slip even when the internal team knows better.
The deeper problem was the ownership split.
One team was driving a lot of front-end delivery on a stack they did not know deeply enough. My team owned the backend platform and the integrations that made the system work in production. But we were not in a strong enough position early enough to define the technical rules of the road.
That is the kind of split that creates trouble even when everybody is acting in good faith.
I do not think migrations are a good place to outsource end-to-end engineering ownership unless the guardrails are very clear and the partner has real depth in the stack.
Strategy can be outsourced. Production support can be outsourced. Parts of implementation can absolutely be outsourced.
But migrations are different. They are full of legacy constraints, business dependencies, odd edge cases, and institutional knowledge that do not show up neatly in project plans.
That is why they go sideways so easily.
What I’d Do Differently Next Time
A few things are obvious to me now.
I would default to reusing proven architecture wherever possible. Rebuilds from scratch should need real justification.
I would separate strategy and design from platform ownership much more clearly. The team that will live with the system should set the engineering standards for that system, especially around backend services and critical integrations.
I would establish hard guardrails up front: repository structure, component patterns, testing minimums, accessibility checks, QA gates, and a real definition of done.
And I would be much stricter about matching ownership to experience. If a partner does not have strong delivery experience with the stack, they should not own end-to-end engineering delivery on a migration.
Would I Use a Partner Again?
Yes.
I would just use one differently.
Partners can be very effective for strategy, production support, self-contained deliverables, and more mechanical migration work like scripts, scaffolding, or content workflows.
Where I would be much more careful is anything that shapes long-term maintainability: architecture, engineering standards, QA discipline, and the kinds of technical decisions the internal team will still be paying for a year later.
We shipped. The site worked. But the real bill came later in cleanup, tech debt, and rebuilding confidence in the platform.
That is the part people tend to underestimate.
If your team does not control the guardrails, your team will still end up paying for the result.