Skip to content
heapbyte - A name of excellence

Migration · 1 October 2026

Shopify protects renames, not deletions

The plan is sensible. Eighteen materials, five product types each, one page per material instead of the couple of hundred that accumulated over six years of adding things. Everyone agrees the result will be easier to maintain, and they are right. Somebody exports the catalogue, builds the new structure, deletes what the new structure replaces, and pushes it live on a Thursday. The following Tuesday organic traffic is down, and nobody can say which pages used to earn it, because the pages are gone.

8 min read
Written by the HeapByte engineering team

Shopify URL Consolidation Guide

Consolidation is deletion

The word “consolidation” does a lot of quiet work. It sounds like merging, or tidying, or bringing things together — and in the catalogue it is all of those. In the URL table it is a delete.

Every product you fold into another one is a product that stops existing, and with it a URL that may have been accumulating links and rankings since before anyone currently at the company joined. The new page is better. It is also new, and the thing that made the old page worth anything does not move across on its own.

It is worth being concrete about what a consolidation of this kind looks like, because the scale surprises people who have only done it for a handful of products. The Custom Floating Shelves rebuild settled on eighteen materials, each with a page carrying five product families — floating shelf, slab, closet shelf, corner shelf and wall hook rack. That is a clean, maintainable structure, and arriving at it meant retiring a much larger set of overlapping product and collection pages that had accumulated over several years, including an older calculator-driven structure that predated the configurator. Roughly 195 redirects came out of it. None of those was optional.

This is why catalogue cleanups have a reputation for losing traffic, and the reputation is undeserved — the consolidation is usually correct. What goes wrong is one step that nobody is prompted to take.

The checkbox you get, and the one you don't

Change a product's handle in the Shopify admin and you are offered a redirect. The checkbox reads create a URL redirect from the old address, it is ticked by default, and you have to deliberately untick it to lose the old URL. The platform assumes you want the redirect, which is the right assumption.

Delete a product and you are offered nothing. The URL 404s immediately — no warning, no prompt, no option. Shopify's own community has asked for the deletion dialog to prompt the way the rename dialog does, which tells you how often people discover this after the fact rather than before.

So the platform protects the safer operation and leaves the destructive one unguarded. Renaming a product, which changes one URL and is easy to reverse, comes with a safety net. Deleting forty products, which destroys forty URLs permanently, does not.

The ceiling is not the problem

Worth closing this off, because it is the first thing people ask. A standard Shopify plan holds 100,000 URL redirects; Plus holds 20,000,000. Redirects can be bulk imported from a CSV in the admin, and Shopify provides a sample template.

A serious consolidation produces a few hundred. The Custom Floating Shelves rebuild prepared about 195 of them across eighteen materials. You are not going to run out, you do not need to ration them, and you should not be deleting redirects to save room. The hard part is not capacity. It is knowing what to point where, and that information only exists before you delete anything.

Build the map before you delete

Which is the whole discipline, and it is an ordering problem rather than a technical one.

Once a product is deleted, the handle is gone from the catalogue. The record of what it was, what it sold, and what the sensible replacement would be exists only in the export you took beforehand — and in whatever analytics still remember the URL. Reconstructing a redirect map afterwards means working backwards from a 404 log, which finds the URLs that still get traffic and silently misses every URL whose traffic you already lost.

So the sequence is: export, decide the target for every handle that will not survive, generate the redirect map, verify it against the list of handles that will survive, import it, and only then delete. The deletion is the last step, not the first, and it is the only irreversible one.

Products are also not the whole job. A catalogue that grew unsupervised for years accumulates overlapping collections as readily as overlapping products, and a collection URL that has been linked to from outside the site is worth exactly as much as a product URL that has. They tend to get forgotten because the consolidation is usually discussed in terms of products, and because a collection that still renders — just with the wrong things in it — does not announce itself the way a 404 does.

ts
type Move = { from: string; to: string };

type Redirect = { from: string; to: string };

type Problem =
  | { kind: "self-redirect"; handle: string }
  | { kind: "missing-target"; from: string; to: string }
  | { kind: "duplicate-source"; from: string; targets: string[] }
  | { kind: "loop"; handles: string[] };

const path = (handle: string) => `/products/${handle}`;

/**
 * Turns a consolidation plan into a flat redirect map.
 *
 * Chains are resolved rather than reported: if A is folded into B and B is
 * later folded into C, the plan emits A -> C, not A -> B. Shopify will serve
 * a chain, but every hop is a round trip and a chain is one broken link away
 * from a dead end.
 */
export function redirectPlan(
  moves: readonly Move[],
  surviving: ReadonlySet<string>,
): { redirects: Redirect[]; problems: Problem[] } {
  const redirects: Redirect[] = [];
  const problems: Problem[] = [];

  // Index the moves, catching sources claimed more than once.
  const target = new Map<string, string>();
  const claimed = new Map<string, string[]>();
  for (const m of moves) {
    const from = m.from.trim();
    const to = m.to.trim();
    if (from === "" || to === "") continue;
    claimed.set(from, [...(claimed.get(from) ?? []), to]);
    target.set(from, to);
  }
  for (const [from, targets] of claimed) {
    if (new Set(targets).size > 1) {
      problems.push({ kind: "duplicate-source", from, targets: [...new Set(targets)].sort() });
    }
  }

  for (const from of [...target.keys()].sort()) {
    const seen = [from];
    let to = target.get(from)!;

    // A row pointing at itself is technically a one-element cycle, but calling
    // it a loop buries the obvious fix under the harder one. Check it first.
    if (to === from) {
      problems.push({ kind: "self-redirect", handle: from });
      continue;
    }

    // Follow the chain to its end.
    while (target.has(to)) {
      if (seen.includes(to)) {
        problems.push({ kind: "loop", handles: [...seen.slice(seen.indexOf(to)), to] });
        to = "";
        break;
      }
      seen.push(to);
      to = target.get(to)!;
    }
    if (to === "") continue;

    if (!surviving.has(to)) {
      problems.push({ kind: "missing-target", from, to });
      continue;
    }
    redirects.push({ from: path(from), to: path(to) });
  }

  return { redirects, problems };
}
The chain flattening is the part that earns its place, and it only matters the second time you consolidate. The first pass folds thirty products into five and every redirect is one hop. A year later somebody folds two of those five into one, and the products redirected in the first pass now point at a URL that redirects again. Shopify will serve that, but each hop is a round trip, and the chain breaks completely the day somebody deletes the middle. Resolving to the final survivor at build time costs four lines and removes the problem permanently. The surviving set is the other guard: a redirect whose target does not exist is worse than no redirect, because it converts a 404 into a 404 with extra steps and looks handled on the spreadsheet.

Chains, loops and the second consolidation

The first consolidation is the easy one. Everything folds into something that exists, every redirect is one hop, and the map is obviously correct.

The trouble starts the second time, because now some of the things you are folding are themselves redirect targets. Fold B into C when A already points at B and you have a chain: A to B to C. Shopify will serve it. It costs an extra round trip, and it breaks entirely the day somebody removes B for being empty — which they will, because B is empty.

Worse, and more common than it should be, two people consolidating different parts of the catalogue can produce a loop. A points at B and B points back at A, and the URL serves nothing useful to anybody. Neither of these is detectable by looking at a redirect list, because every individual row looks fine. They are only visible when the whole map is resolved at once.

What a redirect cannot save

Being honest about the limit: a redirect preserves the path, not the promise.

If you fold a page about one specific thing into a page about eighteen things, the redirect works perfectly and the result can still be worse, because the destination no longer answers the question the original page answered. A redirect tells a search engine where something went. It does not make the destination relevant to what the original ranked for.

That is an argument for consolidating carefully rather than for not consolidating. The products worth keeping as their own page are the ones that answer a question no other page answers, and that judgement is editorial, not technical. A redirect map cannot make it for you — it can only make sure that whatever you decided does not quietly cost you the rest.

When this needs an engineer

It does not need one at twenty products. Change the handles, keep the pre-ticked checkbox, delete carefully, and spot-check the URLs afterwards. That is a morning's work and the admin gives you everything you need.

It needs engineering when the catalogue is large enough that nobody can hold the map in their head — when products are being deleted rather than renamed, when a previous consolidation has already left redirects in place, and when the thing being restructured is also the catalogue you are still importing into. At that point the redirect map is a build artefact that should be generated, verified and version controlled, not a spreadsheet somebody maintains by hand. That work sits with our other migrations and catalogue data engagements, and it is part of what Shopify migration services covers when the restructure is the project.

Send us the store and the symptom.

Insights

Apply this to your store.

An audit turns the general principle into a specific list of changes, ordered by what actually pays back.