Tag: SEO

  • Core Web Vitals for Shopify: a practical fix list

    Core Web Vitals for Shopify: a practical fix list

    Most Core Web Vitals advice for Shopify is either “install a speed app” or a wall of theory. This is the middle version: what each metric means for a storefront, and the specific changes that move it, ordered by impact.

    Before you start, get real numbers. The Shopify speed score in your admin is a lab test on a synthetic device — useful for spotting regressions, useless for knowing what your customers experience. Use the Chrome UX Report data in PageSpeed Insights, which reflects real visits to your store over the previous 28 days.

    LCP — Largest Contentful Paint

    What it measures: how long until the biggest thing above the fold has painted. On almost every Shopify homepage this is the hero image. On a product page it is usually the main product photo.

    Target: under 2.5 seconds for 75% of visits.

    Fixes, in order of payoff

    1. Stop lazy-loading your hero. This is the most common own-goal in Shopify theming. loading="lazy" on the LCP image tells the browser to deprioritise the one thing the metric measures. Your hero should be loading="eager" with fetchpriority="high". Everything below the fold should be lazy.

    2. Serve an image sized for the device. A 2400px hero delivered to a 390px phone is roughly 30× more pixels than needed. Shopify’s image_url filter with a proper srcset and a correct sizes attribute solves this:

    {% assign img = section.settings.hero_image %}
    <img
      src="{{ img | image_url: width: 1200 }}"
      srcset="{{ img | image_url: width: 480 }} 480w,
              {{ img | image_url: width: 800 }} 800w,
              {{ img | image_url: width: 1200 }} 1200w,
              {{ img | image_url: width: 1800 }} 1800w"
      sizes="(min-width: 1024px) 100vw, 100vw"
      width="{{ img.width }}"
      height="{{ img.height }}"
      loading="eager"
      fetchpriority="high"
      alt="{{ img.alt | escape }}">

    The sizes attribute is the part people get wrong. If it does not describe the real rendered width, the browser picks the wrong candidate and all the work above is wasted.

    3. Preload the hero. One line in theme.liquid, inside the {% if template == 'index' %} branch so you are not preloading a homepage image on every page.

    4. Audit your app scripts. Open DevTools → Network, filter to JS, sort by size. Anything over 50KB that is not your theme deserves a question. Apps you trialled and abandoned frequently leave script tags behind — check Online Store → Themes → Edit code for orphaned snippets.

    5. Self-host fonts or use font-display: swap. A render-blocking font request from a third-party origin adds a connection setup you cannot control. If the hero text is the LCP element, this is directly on the critical path.

    INP — Interaction to Next Paint

    What it measures: how long the page takes to visibly respond after a tap or click. It replaced First Input Delay because FID only measured the first interaction and only measured the delay, not the response.

    Target: under 200ms for 75% of visits.

    This is the metric Shopify stores fail most often, and it is almost always apps. Every app that adds a global click listener, every chat widget that boots on load, every review widget that re-renders the page — they all compete for the same main thread that needs to handle the tap.

    Fixes

    1. Count your apps honestly. Open your storefront and list every third-party feature on the page. For each one, ask whether it earns its place. The median store we audit is running two apps it forgot it installed.

    2. Defer everything non-critical. Chat widgets, review carousels, popups and analytics should not load during the initial render. Most modern apps support an idle-load or on-interaction mode; a surprising number just do not have it turned on.

    3. Break up long tasks. If you have custom JavaScript doing work over a large collection, chunk it. Anything holding the main thread for more than 50ms is a long task and directly hurts INP.

    4. Watch for layout thrash in sticky headers. Reading getBoundingClientRect() in a scroll handler and then writing a style forces synchronous layout on every frame. Batch reads and writes, or use IntersectionObserver instead.

    CLS — Cumulative Layout Shift

    What it measures: how much visible content jumps around while the page loads.

    Target: under 0.1.

    CLS is the easiest of the three to fix because the causes are finite:

    • Images without dimensions. Always set width and height (or a CSS aspect-ratio). The browser then reserves the box before the file arrives.
    • Web fonts swapping. A fallback font with different metrics reflows the text when the real font lands. Use size-adjust and ascent-override in your @font-face to match metrics, or a tool that generates them.
    • Injected banners. Announcement bars, cookie notices and free-shipping bars that insert themselves at the top of the DOM after paint push everything down. Reserve their height in CSS or render them server-side.
    • Embedded content. Reviews, Instagram feeds and video embeds that expand after loading. Give them a min-height.

    What to do this week

    If you only have an afternoon:

    1. Run PageSpeed Insights on your homepage, a collection page and your best-selling product page. Record the field data numbers.
    2. Fix loading and fetchpriority on your hero image.
    3. Add width and height to every image in your theme that lacks them.
    4. Uninstall one app you are not using, and check the theme code for what it left behind.
    5. Re-measure in 28 days, because field data is a rolling window and will not move overnight.

    That list has, in our experience, moved more stores from red to green than any speed-optimisation app ever has.

    If you want to know what a small app looks like, all six of ours publish their bundle size on their pages. Ours are under 30KB. Most are not.

  • Shopify SEO: collection pages are the pages you are neglecting

    Shopify SEO: collection pages are the pages you are neglecting

    Most Shopify SEO effort goes into product pages and blog posts. Meanwhile the collection page — the one that should rank for “womens merino base layers”, the highest-commercial-intent search in that category — ships as a title, a grid, and nothing else.

    This is backwards. Collection pages are how category searches get satisfied, and category searches are where the money is.

    Why collections outrank products for category terms

    Search for a category term and look at what ranks. It is almost never a single product page. It is category pages, because they match the intent: the searcher wants to compare options, not buy a specific SKU.

    A product page competing for “merino base layers” is competing with one item against pages offering forty. It loses. The collection page is the right page for that query, and on most Shopify stores it is the least developed page on the site.

    What a collection page needs

    An introduction that is genuinely useful

    Not 300 words of keyword-stuffed filler beneath the grid where nobody reads it. Two or three short paragraphs above or beside the grid that help someone choose:

    • What is in this category and how the options differ
    • What to consider when choosing (weight, fit, use case)
    • A pointer to the obvious default for someone who does not want to think

    This copy does double duty. It gives search engines something to understand the page by, and it helps the shopper who is genuinely undecided — who is the person the page exists for.

    Shopify lets you put this in the collection description field. If your theme renders that description in a tiny box under the header, edit the section to give it room.

    A title and meta description written for the query

    The default is {{ collection.title }} – {{ shop.name }}. Do better:

    {%- if collection.metafields.seo.title -%}
      {{ collection.metafields.seo.title }}
    {%- else -%}
      {{ collection.title }} | {{ shop.name }}
    {%- endif -%}

    A metafield gives you per-collection control without touching code again. Write titles that include the qualifier people actually search — “Merino Base Layers for Women | Free UK Delivery” beats “Base Layers”.

    Faceted navigation that does not create a crawl swamp

    Shopify’s filter URLs (?filter.v.option.size=M) can generate thousands of near-duplicate URLs. Google will crawl them, find near-identical content, and spend your crawl budget on it.

    Two things to do:

    1. Make sure filtered views carry a canonical pointing at the unfiltered collection.
    2. Disallow the filter parameters in robots.txt if you are not deliberately targeting the filtered combinations.

    If a specific filtered view does deserve to rank — “black merino base layers” is a real search — do not rely on the parameter URL. Create a real collection for it with its own title, description and canonical.

    Structured data

    Collection pages support CollectionPage and ItemList markup. This is not a ranking factor on its own, but it helps search engines understand the page as a list of products rather than an undifferentiated blob of text.

    {
      "@context": "https://schema.org",
      "@type": "CollectionPage",
      "name": "Women's Merino Base Layers",
      "url": "https://example.com/collections/womens-merino-base-layers",
      "mainEntity": {
        "@type": "ItemList",
        "numberOfItems": 24,
        "itemListElement": [
          { "@type": "ListItem", "position": 1, "url": "https://example.com/products/…" }
        ]
      }
    }

    Internal links that go somewhere

    Related collections, a link up to the parent category, and links down to sub-collections. Most Shopify stores have a flat collection structure with no internal linking between collections at all, which means every collection is an island reachable only from the nav.

    Pagination handled properly

    If your collection paginates, each page needs a self-referencing canonical (not one pointing at page 1 — that has been wrong since Google dropped rel=prev/next support). Page 2 is a different page with different products; treat it as one.

    Infinite scroll needs paginated URLs behind it or the products beyond the first screen are invisible to crawlers.

    The content that actually earns links

    Here is the part most stores skip. The collection pages that outperform are usually the ones with a genuine buying guide attached — not a blog post linking to the collection, but content on the collection page itself.

    For a base layer collection: a short section on the weight ranges and what conditions each suits. For a coffee collection: the roast levels and what they taste like. This is the content that gets linked to, and the links land on the page you want to rank rather than on a blog post two clicks away.

    A realistic order of operations

    You probably have more collections than time. Prioritise:

    1. Find your top 10 collections by revenue. Shopify Analytics → Sales by collection.
    2. Search their primary term and see what ranks. If it is competitors’ collection pages, you have a real opportunity.
    3. Write the intro copy for those 10. Two paragraphs each. One afternoon.
    4. Fix titles and meta descriptions on the same 10.
    5. Add structured data site-wide. It is one theme edit.
    6. Sort out filter canonicals and robots rules. Also one edit, and it protects everything else you do.

    Then leave it for eight weeks. Collection page improvements do not show up next Tuesday; they show up when the page gets recrawled and re-evaluated, which for a mid-sized store is measured in weeks.

    What not to bother with

    • Keyword density. Not a thing. Write for the person choosing.
    • Duplicating the intro copy across collections with the term swapped. Search engines are good at noticing this, and it makes your store worse for humans.
    • Hidden text below the fold. If you are hiding it because it is bad for shoppers, it is not helping.

    Collection pages are unglamorous work. They are also, on most Shopify stores, the largest pile of unclaimed organic traffic available.