Writing · Case studies
How I built 3,219 redirects for a broken Shopify catalogue
7,172 dead URLs sorted into eight patterns from a sample of 1,000, and 70.5% of a Shopify store carrying no meta title at all. What the cleanup took.
In February 2026 I audited a Melbourne kitchenware retailer’s Shopify store for Emote Digital. The back end held 12,954 product records: 10,430 active, 2,455 archived and 69 draft. Around 3,300 of them were published to the Online Store channel at any one time, which is a distinction worth keeping straight, because active and published are not the same thing and the gap between them is where a lot of this went wrong.
7,267 of those products had no SEO meta title and no meta description at all. Not a bad one. None. That was 70.5% of the catalogue.
This is what that kind of neglect looks like from the inside, and what it took to dig out.
The audit
Screaming Frog across 3,186 crawlable pages, 7,659 keyword records from Ahrefs, 299 referring domains, GA4 year on year, and GSC coverage. The findings that mattered:
| Products with no meta title or description | 7,267, being 70.5% |
| Keyword rankings lost against the prior period | 6,698 |
| Referring domains flagged as spam | 230 of 299, being 76.9% |
| Domain rating | 8 |
| Title tags over 60 characters | 1,912 |
| Meta descriptions over 156 characters | 1,665 |
| Duplicate meta descriptions | 122 |
| Pages receiving zero organic clicks | 82% |
| Organic CTR | 1.27% against a 2.5 to 3.5% benchmark |
| Average SERP position | 17.7 |
| Largest Contentful Paint | 4,034ms against a 2,500ms threshold |
Those are starting conditions in February 2026, not the state of the site today. Most of it was resolved during the engagement. The performance line was not: I never got to the Core Web Vitals work, and I will come back to that.
What the numbers actually meant
Three of those lines matter more than the rest, and not the ones people usually pick out.
82% of pages getting zero organic clicks is the one I would lead with, because it reframes everything else. This is not a site that ranks badly. It is a site where four fifths of the published pages are doing nothing at all. The question stops being “how do we improve these rankings” and becomes “which of these pages should exist”.
A domain rating of 8, with 76.9% of referring domains flagged as spam, is not a link building problem. It is a cleanup problem. Chasing new links while three quarters of the profile is junk is spending money at the wrong end. More on what I did about that below.
6,698 rankings lost against the prior period says this is recent and ongoing rather than old neglect that had stabilised. Something was actively breaking.
The 404s explained a good deal of that.
7,172 URLs returning 404
Search Console reported 7,172 of them. I sampled 1,000, sorted them into eight distinct patterns by URL shape, and extrapolated across the full set. These are estimates from that sample, not counts of all 7,172:
| Pattern | Est. URLs | Share |
|---|---|---|
?variant plus country plus currency, the Shopify currency switcher |
4,812 | 67.1% |
| No parameters, archived or deleted product handles | 1,635 | 22.8% |
?variant only, from old links and backlinks |
481 | 6.7% |
?s=rec&w=p, the Shopify recommendation widget |
143 | 2.0% |
?currency plus variant |
57 | 0.8% |
?stkn, an app-generated stock token |
22 | 0.3% |
?utm_source=shop_app, the Shop app feed |
22 | 0.3% |
Old /collections/X/products/Y URL format |
7 | 0.1% |
Two thirds of it was the currency switcher generating parameterised URLs for products that no longer existed. That is not a content problem or a strategy problem. It is platform behaviour nobody had accounted for, quietly manufacturing dead URLs at scale.
The second pattern, at 22.8%, was archived and deleted products. I pulled the full Shopify export and found 2,441 inactive products: 2,372 archived and 69 draft. Every one a former URL with whatever equity it had earned, pointing nowhere.
The redirect map
3,219 redirects, in Shopify’s Path and Target CSV format:
- 755 to clean product pages, where the product was still live and only the parameters needed stripping
- 2,404 to brand collection pages, so someone looking for a discontinued Jellycat lands on the Jellycat collection rather than a 404
- 60 to
/collections/all, for products I could not brand-match. I flagged those for manual review rather than pretend a generic destination was a decision
Sending a dead product to its brand collection is the part worth arguing for. The lazy version sends everything to the homepage, which tells Google the URL is gone and tells the visitor to start again. The brand collection keeps the intent intact: they wanted that thing, here is the shelf it lived on.
Disavowing three quarters of the backlink profile
230 of the 299 referring domains were flagged as spam. I disavowed all 230, on 16 March 2026, which left the site with 69 referring domains it had actually earned.
That is a drastic-looking number and I want to defend it properly, because disavow files are contested and the objection is a fair one.
Google has spent years telling people most sites do not need a disavow file, and for most sites that is correct. Your handful of dodgy directory links are almost certainly being ignored already. Filing a disavow because a tool coloured some rows red is cargo cult SEO.
A profile that is 77% spam at domain rating 8 is not most sites. At that ratio the question stops being “will Google ignore these” and becomes “what does this profile say about the domain”. And the practical argument is simpler still: until the junk is gone you cannot tell whether link acquisition is working, because every measurement is taken against a baseline of noise.
So the disavow was not the remedy. It was what made the next twelve months of link work measurable.
The 100 row problem
Shopify’s redirect import UI accepts 100 rows at a time.
3,219 redirects is 33 uploads. I built the 33 batched CSVs, because a plan you cannot execute is not a plan. Then I wrote a Python script to push all 3,219 through the API in one unattended run of about 36 minutes.
That script stayed a script. It did one job, it did it once, and it did not need to be anything more. Not every piece of tooling deserves to become software.
The orphaned products
The 7,267 products with no metadata could not be solved by a script, and this is where the work slowed down.
Some of them should get metadata. Some should be archived, because they are discontinued and nobody is searching for them. Some are seasonal and will come back. A tool cannot tell you which is which, and guessing at that scale means either writing metadata for thousands of products that should not exist, or archiving products the client still sells.
So I built a decision workbook instead: all 7,267 rows, grouped by brand, with a keep, optimise or archive column, colour-coded instructions, and an email setting out the three options and asking for decisions brand by brand rather than product by product.
Brand by brand is what made it answerable. Seven thousand product decisions is not a task anyone finishes. Sixty brand decisions is an afternoon.
The part that did become software
Once those decisions came back, something had to apply them, and that is the piece that grew into a proper application rather than a script.
It runs against the Shopify API, and it covers products, collections and pages rather than products alone: SEO titles, SEO descriptions and body copy, plus image alt text, which on a retail catalogue is usually either empty or the filename.
Then there was the part that would have made a bought tool useless here.
The store ran Booster SEO. Like several Shopify SEO apps, Booster does not write to Shopify’s native SEO fields. It keeps its own title and description in metafields, and those override the native ones at render time. So you can bulk-update every native SEO title in the catalogue, watch the job complete successfully, load the page, and see absolutely nothing change.
Any tool that does not know Booster is there will lie to you about having worked. Mine carries three extra columns beside the native fields on every row, for Booster’s raw JSON, its description and its override flag, and it reconciles them.
That is the sort of thing you only find by checking the rendered page against the field you just wrote, which is a habit worth having regardless of platform.
The distinction between this and the redirect script is worth drawing, because it is the line I use to decide whether something should become software at all. The redirect job was finite: 3,219 rows, once, done. The metadata job is not. Products get added, descriptions get rewritten, a brand gets restructured, and the same work comes round again. Recurring work with a stable shape is what earns an application, which is the same test that turned my client reporting into a pipeline rather than a monthly export. One-off work gets a script and a note explaining what it did.
Triage, and the one I left
The 4,034ms Largest Contentful Paint is still on that list. Core Web Vitals work on a Shopify theme is a scoped project rather than a morning: app audits, theme-level template changes, image pipeline decisions, and usually an argument about which of the eleven installed apps has earned its render-blocking script. The engagement ended in March 2026 before it got one.
That was a triage call and I would make it the same way again. The metadata gap and the 7,172 dead URLs were bleeding daily and compounding. A slow LCP was costing conversion but was stable and not getting worse. With finite months you take the ones still deteriorating first.
What the numbers show
Over my time on the account, September 2025 to March 2026, top-ten rankings went from 311 to 366, about 18%, and estimated traffic value rose around 5%. The keyword count fell 6%. Those are DataForSEO estimates rather than measured traffic.
Between March and August 2026, on a partial August snapshot, top-ten rankings roughly doubled. That is three to five months after my last day with other people on the account, so it is not a figure I would put on a resume. It is also what redirect and metadata work at this scale does: it compounds as Google re-crawls, over months rather than weeks, and this account was still compounding after I left it.
The diagnosis and the remediation are the parts I would claim. The 404 patterns were invisible until someone sorted a thousand URLs by shape. The redirect map existed because somebody decided a dead Jellycat product should land on the Jellycat shelf. And the 7,267 orphans became answerable once the question was reframed from products to brands.
Get in touch
Tell me about the role and I will come back to you. If you would rather not use a form, I am on LinkedIn.