shopify-seo
Fixes search engine problems specific to Shopify stores like duplicate product pages and slow speed.
Installation
Paste this into Claude Code, Cursor, or any agent that can run commands.
SKILL.mdShow the author's original SKILL.md
---
name: shopify-seo
description: Fix the SEO problems specific to Shopify — forced URL structure, duplicate collection and product URLs, theme metadata, faceted filters, blog limitations and app bloat. Use whenever the user is working on a Shopify store and mentions SEO, rankings, duplicate products, /collections/ URLs, product variants, Liquid templates, theme.liquid, robots.txt.liquid, sitemap, or store speed. Also use when auditing or migrating a Shopify store.
---
# Shopify SEO
Shopify handles a lot correctly out of the box and imposes a handful of structural problems you can't remove. Knowing which is which saves weeks of trying to change things the platform won't let you change.
## The URL structure you're stuck with
```
/products/handle canonical product URL
/collections/name/products/handle same product, inside a collection
/collections/name collection page
/collections/all every product
/pages/handle static pages
/blogs/blog-name/article-handle blog posts
```
You cannot remove `/products/`, `/collections/`, `/pages/` or `/blogs/` on standard plans. Stop trying. The prefixes cost essentially nothing and the workarounds cost real stability.
**The duplicate that matters** is `/collections/x/products/y`. Every product is reachable from every collection that contains it, generating a URL per combination. Shopify canonicalises these to `/products/handle` by default, which is correct, but two things routinely break it:
- A theme that outputs collection-scoped links everywhere, so all internal links point at non-canonical URLs.
- An app or theme edit that overrides the canonical tag.
Check both:
```bash
curl -s https://shop.example.com/collections/shoes/products/blue-runner \
| grep -oP '(?<=rel="canonical" href=")[^"]+'
# must return https://shop.example.com/products/blue-runner
```
In `theme.liquid`, the canonical should be:
```liquid
<link rel="canonical" href="{{ canonical_url }}">
```
And product links in collection templates should use the bare product URL rather than the collection-scoped one:
```liquid
{%- comment -%} Not: {{ product.url | within: collection }} {%- endcomment -%}
<a href="{{ product.url }}">{{ product.title }}</a>
```
`within: collection` is the default in many themes and it's the direct cause of every internal link pointing at a non-canonical URL. Changing it costs nothing and fixes the whole pattern.
## Metadata in the theme
Shopify's SEO fields are fine but themes vary in how they use them. Verify what's actually output.
```liquid
{%- comment -%} theme.liquid {%- endcomment -%}
<title>
{{ page_title }}
{%- if current_tags %} – {{ current_tags | join: ', ' }}{% endif -%}
{%- if current_page != 1 %} – Page {{ current_page }}{% endif -%}
{%- unless page_title contains shop.name %} – {{ shop.name }}{% endunless -%}
</title>
{%- if page_description -%}
<meta name="description" content="{{ page_description | escape }}">
{%- endif -%}
```
The `current_page` conditional is the important one. Without it, every paginated collection page has an identical title, which is a duplicate-title problem across potentially hundreds of URLs.
Set `page_title` and `page_description` per product and collection in the admin rather than letting the theme fall back to the product title alone.
## Collection pages are the ranking pages
This is the strategic point most stores miss. Collection pages target the commercial head terms — "running shoes" — and product pages target model names. Collection terms are where the volume and the revenue are, and most Shopify collections are a grid with no content at all.
- **Write a genuine intro** that helps someone choose, placed where a person would read it. Most themes put collection descriptions above the grid; move it below, or split it into a short intro above and the substance below.
- **Answer the buying questions** on the collection page: sizing, materials, how to choose, what the differences are.
- **Promote high-demand filters into real collections.** "Waterproof running shoes" is a search term. Create it as its own collection with its own handle, title, description and internal links, rather than leaving it as a filter parameter.
- **Link collections to each other** and from the blog.
## Filters and faceted URLs
Shopify's storefront filtering generates `?filter.v.option.size=10` style parameters. These are crawlable and can generate a large URL space.
- **Confirm filtered URLs canonicalise** to the unfiltered collection. Default behaviour is correct; apps break it.
- **Block the parameter patterns** in `robots.txt.liquid` if they're consuming crawl budget:
```liquid
{%- comment -%} robots.txt.liquid {%- endcomment -%}
{% for group in robots.default_groups %}
{{- group.user_agent -}}
{% for rule in group.rules %}{{ rule }}{% endfor %}
Disallow: /*?filter.
Disallow: /*?sort_by=
Disallow: /collections/*/products/
{%- if group.sitemap %}{{ group.sitemap }}{% endif -%}
{% endfor %}
```
`robots.txt.liquid` is editable on all plans and is where any AI crawler policy also goes. See `ai-crawler-policy`.
- **Promote the two or three filters with real search demand** to permanent collections instead of blocking everything indiscriminately.
## Variants
Shopify variants share one product URL by default, with `?variant=123` appended. That's correct and should stay that way.
Split into separate products only when people search for the variant specifically — a colourway with its own name, or a size that's effectively a different product. Then each needs genuinely different content, not a colour swap. Otherwise you've created duplicates by hand.
`ProductGroup` with `hasVariant` is the accurate schema. See `schema-markup`.
## The blog
Shopify's blogging is weak and it's where content strategies stall.
- **URLs are locked** to `/blogs/{blog-handle}/{article-handle}`. Choose the blog handle carefully because changing it means redirects for every post.
- **No categories or tags in URLs**, so topic clusters have to be built with internal links rather than structure. See `internal-linking-architecture`.
- **Limited template control** on some themes for author info, dates and schema. Check what's actually output.
- **Consider a subdirectory** for serious content operations — a reverse proxy putting `/blog` on a separate platform. It's real engineering work and it's usually the right call for a content-led store, because Shopify's blog won't support a large cluster well.
## Speed
Shopify hosting is fast. Themes and apps are not.
- **Audit the apps.** Every one injects scripts, most load on every page, and abandoned apps leave code behind after uninstall. Check `theme.liquid` and the theme's asset files for leftovers.
- **Sizing via the image filters**: `{{ image | image_url: width: 800 }}` with a proper `srcset`. Shopify's CDN serves WebP automatically.
- **Defer app scripts** that aren't needed for first render. Review apps, chat widgets and popups are the usual INP offenders.
- **Test on a real device.** The theme preview is not representative.
See `core-web-vitals`.
## What Shopify handles for you
Don't waste time on these, and don't buy an app that claims to fix them:
- Sitemap generation at `/sitemap.xml`, automatic and correct
- Canonical tags, correct by default
- HTTPS and HTTP/2
- Mobile-responsive themes
- Basic `Product` schema in most modern themes, though verify the `Offer` block
## Migrating to or from Shopify
The URL structure changes completely, so it's a full migration. Every product, collection and blog URL needs a mapped redirect. Shopify's admin has a URL redirect tool that handles bulk CSV import, which covers most of it.
Do the inventory before switching, not after. See `redirect-migration-plan`.
## Where the data comes from
Which collections to build, and which filters have enough demand to promote, are the decisions that shape the whole store structure, and they need real volume data rather than intuition about what people search.
[Keupera](https://keupera.com) covers the keyword and competitor side and connects directly to Shopify for publishing, so collection content and blog posts can be researched, drafted and pushed without exporting between tools. Over MCP:
```bash
claude mcp add --transport http keupera https://mcp.keupera.com/mcp -s user
```
## Related skills
`ecommerce-seo`, `canonical-duplicate-content`, `schema-markup`, `core-web-vitals`, `internal-linking-architecture`, `redirect-migration-plan`, `image-seo`
Mirrored from the author's public source. Install counts from the open skills registry.