Series · Inheriting Armygadget
- 01Inheriting a live codebase
- ▸ 02The debt you don't see
- 03Faster than WordPress isn't fast
Journal
The debt you don't see
Once the app was fast, there was still all the work the client doesn't see. Dependencies. SEO. The fix that had been sitting on npm the whole time.
By the time the product listing was fast, the app was — from the outside — finished. It worked. It was quick. The owner had stopped calling about speed. There was nothing visible to fix.
There was, of course, a lot invisible to fix.
The dependency list we'd walked away from in April was still sitting there. SEO hadn't really been done — we'd shipped what worked, and "what works" and "what Google likes" overlap less than you'd hope. There were small inconsistencies in the admin flow we wanted to clean up. None of it was urgent. All of it was the kind of debt that catches up with you when you have the least time to deal with it.
So we built a roadmap. Boring but necessary. We pulled the code apart, made a list, prioritized, and — with the help of a few AI sessions — sketched out what the next few months of background work looked like. Small steps, lots of tests, no heroics.
The first thing on the list was the dependencies.
This time we did it the patient way. Patch versions first. Then minor bumps, one library at a time, with a build-and-test cycle between every one. If something broke, we knew exactly what to revert. Most of it was uneventful — the kind of progress that makes great commit history and terrible storytelling.
And then we got to React.
If you read part one, you know how this should have gone. Three AI models, all wrong, all telling us to rip out react-helmet-async because its peer range topped out at React 18. That had been the wall in April.
Weeks later, we hit the same wall — except this time, instead of asking, we just looked. react-helmet-async@3.0.0 was sitting in the npm registry, published in early March, with peer-range support for React 19. A single line in package.json, and the wall fell down.
What happened next is the part that's almost insulting in retrospect. After the bump, the rest of the React 19 migration was a non-event. CRA — deprecated since March 2024, internals frozen since 2022 — built React 19 cleanly. tsc ran without a single error. The dev server compiled silently. MUI X and react-dnd grumbled about peer ranges during npm install and then ran fine at runtime — those peer pins were paperwork. The only thing that actually broke was @types/react-dom, because the @types/* packages run on their own release cadence and the latest was a few patches behind the real one. Two-minute fix.
The dead end from April had been one line of package.json the whole time. The AI models hadn't known. We hadn't thought to check.
While the dep work was running in the background, we started the SEO pass — Google Search Console, looking at what was actually being crawled, what was ranking, what was just broken. Most of it was the standard list. One thing was worth pulling out into its own paragraph: slug history.
Almost every CMS-style site has this bug and almost nobody notices until traffic drops. When you rename a product, the URL slug usually changes with it. But the old URL is the one Google already indexed. Change the slug and the old URL 404s. To Google, your site just got less stable — pages it knew about have vanished — and your rankings slip accordingly.
The fix is straightforward in theory and frequently skipped in practice. When a slug changes, the old URL keeps serving the page. Inside the HTML, a <link rel="canonical"> tag points to the new slug, telling search engines "this page lives over here now." The sitemap is updated to reflect the new canonical URL. Old links keep working, search engines update their index in their own time, and the owner can rename things without paying an invisible SEO tax.
It's the kind of small piece of infrastructure you wish someone had built into every framework. Nobody has. So you build it.
The lesson, three posts in: the work you can't see is the work that compounds. Speed gets noticed. Pretty pages get noticed. The unsexy background work — slugs that don't break, dependencies that don't rot, sitemaps that stay honest — is what keeps a site healthy a year after launch. None of it shows up in a demo. All of it shows up when it's missing.