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%
URL patterns behind the 404sHorizontal bar chart of 8 URL patterns behind 7,172 reported 404s, extrapolated from a sample of 1,000. ?variant plus country and currency, the currency switcher, 4,812 URLs at 67.1%, no parameters, archived or deleted product handles, 1,635 URLs at 22.8%, ?variant only, from old links and backlinks, 481 URLs at 6.7%, the recommendation widget, 143 URLs at 2.0%, ?currency plus variant, 57 URLs at 0.8%, an app-generated stock token, 22 URLs at 0.3%, the Shop app feed, 22 URLs at 0.3%, the old collections URL format, 7 URLs at 0.1%.currency switcher?variant plus country and currency, the currency switcher: 4,812 URLs, 67.1%4,812archived or deletedno parameters, archived or deleted product handles: 1,635 URLs, 22.8%1,635old links?variant only, from old links and backlinks: 481 URLs, 6.7%481recommendation widgetthe recommendation widget: 143 URLs, 2.0%143currency plus variant?currency plus variant: 57 URLs, 0.8%57stock tokenan app-generated stock token: 22 URLs, 0.3%22Shop app feedthe Shop app feed: 22 URLs, 0.3%22old collections URLsthe old collections URL format: 7 URLs, 0.1%7
The 7,172 reported 404s, sorted into 8 patterns. Counts are estimates extrapolated from a 1,000-URL sample, not counts of the full set.

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.

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.

    Spam check