WooCommerce migrations are a different animal from Magento. With Magento you usually inherit a clean catalog tree, a finite SKU count, and a developer who once cared about URL structure. With WooCommerce you inherit whatever the previous owner glued together over five years of theme switches, plugin churn, and "let's just make it work" decisions. Permalinks change with the wind. The /shop/ and /product-category/ paths might be there, or they might be hidden behind /buy/, /catalog/, or a translated slug a German tab leaked years ago.
The technical migration to Shopify Plus is not actually harder than Magento — Shopify's URL structure is simpler. The hard part is the legwork of finding what URLs actually exist on the live WooCommerce site, mapping them to Shopify equivalents, and watching the search-console signal during the cutover so you catch the redirects that don't fire. This is the field guide for the SEO portion of a Woo → Shopify Plus migration. The catalog import, theme rebuild, and app porting all live in their own playbooks; here the focus is what keeps the rankings alive.
The WooCommerce URL gap
Shopify's URL structure has four rules and one exception. /products/{handle} for product detail pages. /collections/{handle} for category-style listings. /pages/{handle} for static content. /blogs/{blog-handle}/{post-handle} for blog posts. The exception is /collections/{handle}/products/{handle} — the "scoped" product URL — which exists but is a footgun: Google chooses one canonical anyway, and serving the same product under multiple paths splits link equity.
WooCommerce has no equivalent rule. Out of the box you get /shop/ as the catalog root, /product-category/{slug}/ for taxonomies, and /product/{slug}/ for products. But the moment a WordPress admin opens Settings → Permalinks and clicks the Custom Structure radio, all bets are off. We have seen the following in the wild:
- /shop/{slug}/ instead of /product/{slug}/ — the "shop base" override that ships with the Storefront theme
- /buy/{slug}/ when a previous theme swapped the product slug to read more like an action
- /produkte/{slug}/ when a localisation plugin rewrote the path and nobody put it back
- Nested category paths like /product-category/men/jackets/leather/ that need flattening because Shopify collections do not nest
You cannot just copy the URLs over. You have to enumerate them, classify them, and decide where each one lands on Shopify. Most of the redirect work goes here, not in the cutover itself.
The deeper trap is product attributes. WooCommerce attributes (size, color, material, brand) are often surfaced as URL paths or query strings — /product-category/jackets/?attribute_color=black is a common shape. Those URLs do not exist on Shopify by default. If they were getting indexed and earning organic traffic, you need to recreate them as collection pages with proper handles before cutover, or the redirect target is the parent collection root and the search ranking evaporates.
Run a Screaming Frog crawl of the entire WooCommerce site before doing anything. If you have GSC access, also export the top 1000 indexed URLs sorted by impressions over the last 90 days. The gap between the two lists tells you what was traffic-bearing but not in the obvious crawl path — usually paginated archives, attribute filter URLs, and old blog posts with /?p= permalinks that never got rewritten when the site moved to pretty permalinks.
Mapping URL types
Once you have the full crawl, classify every URL into one of seven buckets. Product detail (/product/X) maps to /products/X. Category (/product-category/X) maps to /collections/X with any nesting flattened. Shop root (/shop) maps to /collections, the all-products page Shopify generates by default. Static pages (/about, /contact, /shipping-policy) map to /pages/X. Blog posts (/2024/03/15/title or /?p=NNNN) map to /blogs/news/title — and we will come back to blog handling shortly. Tag archives (/product-tag/X, /tag/X) map either to /collections/{tag} if the tag is traffic-bearing, or to a 410 if it was thin content. Filter URLs (?orderby=price, ?attribute_color=black, /page/2) need a per-URL decision.
For the filter bucket, the call is whether the URL is indexed AND earning impressions in GSC. If both are true, recreate the destination as a Shopify collection. If only one or the other, 410 it.
The 410 (Gone) is underrated and often skipped. It tells Google "this URL existed and is now permanently gone — please remove it from the index." If you 301 every dead URL to the homepage, Google treats it as a site-wide soft 404 pattern and starts dropping signals. We have seen sites lose 40% of indexed pages this way, with the recovery taking three months instead of three weeks.
Slug normalisation is the next sub-task. WooCommerce slugs often contain stop words and inconsistent casing — "Mens-Black-Leather-Jacket-XL" might be the slug for a product whose Shopify handle should be "mens-black-leather-jacket-xl". You can let Shopify auto-handle that during import, but be aware: the Shopify handle is generated from the title at import time, and if the imported title differs from the WooCommerce one (capitalization, trailing whitespace, an em-dash where there used to be a hyphen), the handle will not match what you redirected to. Set the handle explicitly in your CSV import. Never trust the default.
Internationalised stores are the worst case. If WooCommerce was running WPML or Polylang with /de/, /fr/, /es/ paths, and you are consolidating to a single Shopify store with Shopify Markets, the redirect map balloons by 4-5x. Each variant of every URL has to be mapped to the corresponding locale path on the Shopify side (/de-de/products/X, /fr-fr/products/X, and so on). Don't forget the /de/ blog posts with their own slugs — the /de/2024/03/15/article slug is often a translation, not a transliteration of the English one, which means the mapping cannot be programmatic. It is real translation work.
Building the redirect map
Shopify accepts redirects as a CSV uploaded through Admin → Online Store → Navigation → URL Redirects, or through the Admin GraphQL API. The format is two columns: From, To. The From must be a path with no domain. The To can be a relative path or an absolute URL.
There are three operational gotchas that bite teams shipping their first Woo → Shopify migration.
The first is the 5MB CSV limit, which translates to roughly 100,000 rows depending on path length. We have rarely hit it on a single Woo migration but warnings start appearing around 50K rows. If you have more, batch the upload across multiple files or — better — push them via the Admin GraphQL API in chunks of 5,000.
The second is order-sensitivity. If you have a redirect From: /products/foo To: /products/bar AND From: /products/bar To: /products/baz, Shopify will follow the chain — but only if the rules are uploaded in the right order. Test redirect chains in a staging Shopify development store before pushing to prod. We always upload to a clone first, run the live redirect map through Screaming Frog, and confirm that no chain exceeds two hops.
The third is Shopify's automatic redirect creation. When you publish a product whose handle changes, Shopify auto-creates a redirect from the old handle to the new one. That sounds helpful until you realise it only fires for handle changes within Shopify, not for redirects coming in from a previous platform. Don't rely on it.
A common pattern: ship the redirect CSV to the Shopify admin on the DAY OF cutover, not before. If you upload it earlier and a user (or a search-engine crawler) navigates to one of the new Shopify URLs while the WooCommerce site is still authoritative, the redirect fires and the user lands on a page that does not exist yet. We saw a 40-minute traffic outage from this on a Friday afternoon — the merchant had pre-loaded the redirects for an early start, then changed plans and pushed the cutover to Monday.
For the actual mapping work, the unglamorous tool of choice is a Google Sheet. Column A: WooCommerce URL. Column B: classification (one of the seven buckets above). Column C: Shopify URL. Column D: redirect status (301 / 410 / new). Have someone walk through and approve every row. It is tedious — a 4,000-URL site is two days of human review — and that is the point. The sheet catches the issues you cannot catch programmatically. "This product was discontinued in 2022 but the URL still gets 200 visits/month, let's send it to a similar in-stock product instead of 410ing it." That decision needs a human who knows the catalog.
Handling the WordPress blog
Most WooCommerce stores have a WordPress blog attached. Either /blog/ or /news/ as the parent path, posts at /blog/post-slug/ or /YYYY/MM/DD/post-slug/. The blog often outranks the storefront for informational queries — "how to clean a leather jacket" type content. Losing the blog loses the funnel that feeds product page impressions.
Shopify forces all blog posts into /blogs/{blog-handle}/{post-handle}. You can name the parent blog whatever you want — most teams pick "news", "journal", or "blog". Pick one and commit to it; renaming the blog handle later means another redirect cycle.
The WordPress to Shopify content port is its own task. WordPress posts are HTML; Shopify blog content is also HTML, but it renders through a Liquid template. WordPress shortcodes will not work on the Shopify side — [gallery], [embed], any plugin shortcode renders as literal text on the rendered page. Strip them in your import script before they hit Shopify, or replace them with the appropriate static HTML. The most common offender is the Gutenberg block syntax that Classic Editor sites still emit; those `<!-- wp:paragraph -->` comments survive the export and need a regex pass.
Featured images are the next gotcha. WordPress stores them as a post meta key (_thumbnail_id pointing to an attachment); Shopify wants them as a field on the article. Fetch the featured_image URL from the WordPress REST API (/wp-json/wp/v2/posts/{id}?_embed), upload the binary to Shopify Files, and link it on the article during import. Don't hotlink to the WordPress server — once the WP hosting is decommissioned, the images vanish.
Image preservation is non-negotiable. The blog body usually has 5-15 inline images per post. WordPress stores them in /wp-content/uploads/ with paths like /wp-content/uploads/2023/03/foo-1024x768.jpg. After cutover, those URLs return 404 unless you mirror the entire /wp-content/uploads/ tree to your CDN, OR rewrite every image URL in every blog post to point to a Shopify Files URL. We always do the rewrite. Mirroring is fragile — every WordPress security update can move the path, and the hosting bill creeps because the files are not coming through a CDN.
Comments are a separate question. Shopify's native blog comments are barebones — no threading, no admin moderation queue worth using, no spam filtering beyond Akismet (which you'd have to wire up yourself). If the WordPress site had Disqus or another third-party comments service, that ports cleanly because the embed is just JavaScript with a thread identifier. If it had native WordPress comments, the comments themselves are stored as rows in wp_comments — there is no clean import path into Shopify. The pragmatic call is to export the top-N most-engaged comment threads as static HTML appended to the bottom of each migrated post and disable comments on Shopify going forward. Old conversations stay readable; no new ones get added. SEO-wise, the indexed long-tail keyword content from the comment threads is preserved.
Schema migration
WooCommerce themes typically emit Product schema (schema.org/Product) on product pages, plus the standard Article schema on blog posts. Shopify's default Online Store 2.0 themes also emit Product schema — but the emit may differ in shape from what the WooCommerce site was producing. The mismatch can cost you rich-result impressions if Google had been showing review stars or price snippets for your products.
Audit your current rich-result performance before cutover. GSC → Enhancements → Product snippets shows how many product URLs have valid product schema and how many are showing review stars in SERPs. Note the count. After cutover, watch that count weekly: if it drops by more than 10% week over week, your schema is not matching what Google expected for the same product.
The most common mismatch is review schema. WooCommerce has native product reviews stored in wp_comments with a comment_type of 'review'; many WC themes emit AggregateRating with a count and average. Shopify's default theme does not emit AggregateRating unless you have a reviews app installed (Judge.me, Yotpo, Stamped, Loox — pick one). If you are porting reviews from WooCommerce — and you should, that data is years of trust signals — you need both: import the reviews into the new app AND verify the app's theme integration emits AggregateRating with the same shape (review count, score range, review body) as before.
Product variant schema is another trap. WooCommerce variations are typically modeled as a single Product with hasVariant arrays or a top-level ProductGroup; Shopify variants are auto-emitted as offers within the same Product schema. Same data, different schema graph. Validate with Google's Rich Results Test on a representative sample of products before going live, ideally on a staging Shopify store with a few migrated products in place.
Finally, breadcrumb schema. WooCommerce themes often emit BreadcrumbList from /shop → category → product. Shopify's default rendering of /collections/foo/products/bar produces an analogous breadcrumb if and only if the theme has it wired in the schema partial. Check the theme's templates/product.json or sections/main-product.liquid for a BreadcrumbList JSON-LD block. If it is missing, add it before launch — losing breadcrumb rich results is a small but cumulative loss.
GSC monitoring through cutover
The first 30 days post-cutover is when you find the redirect map's gaps. Submit the new sitemap.xml to Google Search Console on cutover day — same hour, ideally. Watch the coverage report. There are three signals to pre-stage your alerts on.
URLs Google cannot crawl. Check that robots.txt is open (Shopify ships a sensible default but it can be customised — verify yours). Confirm canonical tags point to the new URL on every redirected destination. Make sure no redirect chain ends at a noindex page.
Soft 404s. These are URLs that returned an HTTP 200 but Google decided look like a not-found page based on content patterns. The usual symptom is that you redirected to a thin collection page or to the homepage. Fix by replacing those redirects with 410s, or by pointing them to a destination with substantive content (a parent category, a similar product, a curated list).
Redirect chains. Google penalises chains longer than three hops by simply giving up on following them, which means the destination URL doesn't get the link equity from the original. Run all your live redirect URLs through a redirect-checker. Screaming Frog can do this in batch. The most common cause of unintended chains is a stack of platform-default redirects: www to apex, http to https, Shopify's own handle alias, and your original WooCommerce handle. That stacks to four hops without anyone noticing.
Impressions will drop in week one regardless of how clean your work is. We see 30-50% drops from baseline as Google revalidates. Week two should sit at 65-80% of baseline; week three at 85-95%; week four near full recovery if the redirect map is correct. If you are still below 80% at the end of week three, you have a structural redirect issue and you should be running fresh crawls and diffing the output, not just waiting.
The pattern below is from a recent Shopify Plus migration — apparel store with ~12,000 SKUs, 4,500 indexed pages, a five-year-old WooCommerce install with three previous theme generations layered into the URL slugs. You can see the trough in week one, the gradual climb, and the full recovery by week four.
The other monitoring habit: weekly GSC review for the first 6 weeks. Check the queries report for terms you used to rank on. If a term is missing from the new corpus, the page Google was ranking either did not get indexed (redirect target 404s, or canonical points back at the old URL), or the new content under that URL no longer matches the query intent.
When to call it done
The migration is "done" when four conditions hold for a full week. GSC impressions are back within 5% of pre-migration baseline. Top-100 keyword positions (from your existing rank-tracker) are within 2 positions of pre-migration on average. No URLs in the GSC coverage report remain in the "redirect chain" or "soft 404" buckets. Click-through rate from organic is within 10% of baseline — CTR can drop slightly because the new title tags and meta descriptions are different, and that is fine, but it should normalise.
In our experience, the four conditions take 4-8 weeks to converge for a clean migration of a mid-sized catalog. Larger catalogs (50K+ SKUs) take 6-10 weeks. The deciding factor is rarely the technical work; it is whether the WooCommerce site had clean URLs to start with, or whether the redirect map had to handle five years of slug churn, plugin permalink overrides, and translated path variants.
One last operational note: do not sunset the WooCommerce hosting until the migration is verified done. Keep it running on a temporary domain — staging.oldsite.com behind a noindex header — so you can compare before/after for any URL that turns up in a "where did this rank" investigation. We hold this for 90 days minimum, then archive a static crawl of the entire WooCommerce site to S3 in case we need to reference content during the next 12 months.
Migration projects are won and lost on the 30-day post-cutover work, not the cutover itself. Plan accordingly.
If you are staring at a Woo → Shopify Plus migration and the catalog is bigger than 5,000 SKUs or the URL history is messy, talk to us before you build the redirect map. We will look at your GSC data and tell you which redirect categories are going to bite, which usually saves a week of trial and error.
For the catalog-side counterpart on a different platform, Read the Magento → Shopify Plus playbook . For an end-to-end view of what one of these engagements looks like — discovery, scoping, build, cutover, post-launch — see the migration services overview .
Work with us
Thinking about your next Shopify project?
We build and migrate Shopify stores for brands that care about performance. If this article sparked something, tell us what you’re working on.
Start a conversation