Technical SEO Audit & Code Fixes

Enea Studio

Full-scope technical SEO audit, Liquid theme code fixes, and structured data implementation for a Shopify 2.0 fine jewelry store with 476 products across 191 collections.

679 Pages Audited
25,285 Duplicate Links Found
0/679 Correct H1 Tags
CWV Fix Desktop Vitals

The Problem

Enea Studio is a Shopify 2.0 fine jewelry store selling lab-grown diamond rings, necklaces, bracelets, and earrings. With 476 products across 191 collections, the store had significant organic search visibility potential — but technical SEO issues were undermining it at every level.

The store’s desktop Core Web Vitals were failing in CrUX field data (LCP 2.6s, CLS 0.12), every single page had broken H1 heading hierarchy, collection pages had zero structured data for rich results, and the Liquid theme code was generating 25,285 collection-scoped duplicate product links that diluted crawl budget and created massive indexation bloat.

Step 1: Full-Site Crawl & Indexability Analysis

I ran a custom Python crawler against all 679 sitemap URLs to map the complete indexation landscape.

Crawl Results

Metric Value
Total URLs in Sitemap 679
200 OK Responses 679 (100%)
Products 476
Collections 191
Pages 10
Blog/Articles 2

Critical Finding: Zero Meta Robots Tags

Not a single page across the entire site had a <meta name="robots"> tag. Filter URLs (?filter.p.product_type=, ?sort_by=) were completely open to Google’s crawler — no noindex, no crawl directives. While canonical tags correctly stripped filter parameters, canonicals are hints that Google can override. Without explicit noindex on filtered URLs, crawl budget was being wasted on thousands of parameter combinations.

The fix — conditional noindex injection in theme.liquid:

{% if request.path contains '/collections/' %}
  {% if current_tags or request.url contains 'filter.' or request.url contains 'sort_by' %}
    <meta name="robots" content="noindex, follow">
  {% endif %}
{% endif %}

Step 2: H1 Heading Hierarchy — 100% Broken

The crawl confirmed that 0 out of 679 pages had correct H1 hierarchy. Every page had between 2 and 4 H1 tags.

H1 Count Pages Percentage Pattern
2 H1s 483 71% All products + some pages (logo H1 + page title H1)
3 H1s 196 29% All collections + some pages (logo + title rendered twice, or logo + title + FAQ H1)

Three Sources of H1 Breakage

1. Logo rendering as H1 on every page

The header section rendered the site logo inside an <h1> tag unconditionally. On the homepage this is correct — on every other page it creates a competing H1.

{%- comment -%} BEFORE: H1 on every page {%- endcomment -%}
<h1 class="header__heading">
  <a href="/">{{ shop.name }}</a>
</h1>

{%- comment -%} AFTER: Conditional heading tag {%- endcomment -%}
{% if template == 'index' %}
  <h1 class="header__heading">
    <a href="/">{{ shop.name }}</a>
  </h1>
{% else %}
  <div class="header__heading">
    <a href="/">{{ shop.name }}</a>
  </div>
{% endif %}

2. Announcement bar using H1

The “10% Off Your First Order” promo bar was rendering as an H1 element. Changed to <p> — promotional text is not a page heading.

3. Collection title rendered twice as H1

Every collection page rendered its title as H1 in two separate sections: once in a text banner section and again in the collection header. The duplicate H1 pattern showed “Diamond Rings | Diamond Rings” across all 191 collections. One instance was demoted to a <div>.

Additional H1 Bug: FAQ Content Leak

6 standard pages (Shipping, Privacy Policy, Warranty, Jewelry Care, Accessibility, Returns & Exchanges) had “Frequently Asked Questions” appearing as an extra H1 — a content block using the wrong heading level, leaking an H1 into pages where it didn’t belong.

Step 3: Duplicate Product URL Elimination

The Shopify Duplicate URL Problem

Shopify makes every product accessible via two URL patterns:

  • Canonical: /products/diamond-ring
  • Collection-scoped: /collections/rings/products/diamond-ring

While canonical tags pointed correctly to /products/ paths, the theme’s Liquid code was generating collection-scoped links in product card snippets using the within: collection filter.

Scale of the Problem

Metric Value
Collection-scoped product links 25,285
Pages generating duplicate links 191 (every collection)
Average duplicate links per collection 132
Worst offender /collections/lab-grown-diamond-rings (195 links)

The Liquid Code Fix

Found across 5 product card snippet files with 15 instances of the within: collection filter:

{%- comment -%} BEFORE: Generates /collections/rings/products/handle {%- endcomment -%}
<a href="{{ product.url | within: collection }}">

{%- comment -%} AFTER: Clean canonical product URL {%- endcomment -%}
<a href="/products/{{ product.handle }}">

This single Liquid change across 5 files eliminated all 25,285 duplicate internal links, consolidating link equity to canonical product URLs.

Step 4: Structured Data Implementation

Schema Audit Findings

Page Type Count Schema Present Missing
Products 475 Organization + Product BreadcrumbList, materials, enriched offers
Products (outlier) 1 Organization + WebSite Product schema entirely missing
Collections 191 Organization only CollectionPage, ItemList, BreadcrumbList
Pages 10 Organization only BreadcrumbList

Collections had zero rich-results-eligible schema. Google Rich Results Test confirmed: no items detected on any collection page. This meant 191 pages — representing the primary category navigation — had no chance of appearing with enhanced SERP features.

CollectionPage + ItemList Implementation

<script type="application/ld+json">
{
  "@context": "https://schema.org",
  "@type": "CollectionPage",
  "name": "{{ collection.title | escape }}",
  "url": "{{ shop.url }}{{ collection.url }}",
  "description": "{{ collection.description | strip_html | escape }}",
  "mainEntity": {
    "@type": "ItemList",
    "numberOfItems": {{ collection.products_count }},
    "itemListElement": [
      {%- for product in collection.products limit: 50 -%}
      {
        "@type": "ListItem",
        "position": {{ forloop.index }},
        "url": "{{ shop.url }}/products/{{ product.handle }}"
      }{%- unless forloop.last -%},{%- endunless -%}
      {%- endfor -%}
    ]
  }
}
</script>

Added across product, collection, and page templates:

<script type="application/ld+json">
{
  "@context": "https://schema.org",
  "@type": "BreadcrumbList",
  "itemListElement": [
    {
      "@type": "ListItem",
      "position": 1,
      "name": "Home",
      "item": "{{ shop.url }}"
    },
    {
      "@type": "ListItem",
      "position": 2,
      "name": "{{ collection.title | escape }}",
      "item": "{{ shop.url }}{{ collection.url }}"
    }
    {%- if template contains 'product' -%}
    ,{
      "@type": "ListItem",
      "position": 3,
      "name": "{{ product.title | escape }}",
      "item": "{{ shop.url }}/products/{{ product.handle }}"
    }
    {%- endif -%}
  ]
}
</script>

Step 5: Core Web Vitals Diagnosis & Fixes

CrUX Field Data (Real Users, 28-Day)

Metric Desktop (p75) Status Mobile (p75) Status
LCP 2.6s Needs Improvement 1.9s Good
INP 62ms Good 130ms Good
CLS 0.12 Needs Improvement 0 Good
FCP 2.1s Needs Improvement 1.5s Good
Overall CWV FAILED LCP + CLS failing PASSED All passing

Desktop was the ranking bottleneck — failing on both LCP (threshold: 2.5s) and CLS (threshold: 0.1).

Lighthouse Lab Scores (Mobile)

Page Perf LCP TBT CLS
Homepage 49 4.4s 1,110ms 0.001
Diamond Collection 31 9.0s 2,480ms 0.009
New Arrivals 38 6.6s 1,390ms 0.016
Product (canonical) 38 18.3s 720ms 0.003
Product (collection-scoped) 24 6.1s 1,740ms 0.251

Desktop CLS Fix

The 0.12 desktop CLS was caused by:

  • Images without explicit width and height attributes causing layout reflow
  • Klarna On-Site Messaging widget injecting content after page load
  • Font loading causing FOUT (Flash of Unstyled Text)

Performance Bottlenecks Identified

  • 803KB–1,118KB of unused JavaScript across all pages
  • 62–64KB of unused CSS
  • 67–78KB of legacy JavaScript that could be modernized
  • Excessive preconnect hints (>4 connections) causing resource contention
  • Collection-scoped product pages loading 18,439KB network payload (4x heavier than homepage)
  • Klarna currency mismatch errors (configured EUR, shop currency USD) triggering console errors and unnecessary script execution
  • SmartSize app loading size chart modal JavaScript on every page, not just product pages

Third-Party Script Triage

{%- comment -%} Conditional loading: only load SmartSize on product pages {%- endcomment -%}
{% if template contains 'product' %}
  {{ 'smartsize.js' | asset_url | script_tag }}
{% endif %}

{%- comment -%} Defer Klarna widget to after page load {%- endcomment -%}
<script defer src="https://js.klarna.com/web-sdk/v1/klarna.js"></script>

Step 6: Theme Code Inspection

Systematic review of the DPD theme codebase to map every issue to a specific file and line number:

File Issues Found
theme.liquid Missing meta robots logic, no conditional noindex for filters/sorts, schema injection points needed
header.liquid Unconditional H1 on logo, needs template-conditional heading tag
product-card-*.liquid (5 files) 15 instances of within: collection generating duplicate URLs
collection-template.liquid Duplicate H1 rendering, missing CollectionPage + ItemList schema
product-template.liquid Missing BreadcrumbList schema, Product schema missing materials field
announcement-bar.liquid Promotional text incorrectly using H1 heading tag

Deliverables

The audit report included:

  • Full crawl data — 679 URLs with H1 counts, canonical tags, schema types, response codes, and page sizes exported to CSV
  • Prioritized fix list — every issue scored by SEO impact (Critical / High / Medium) with estimated implementation hours
  • File-by-file code change specifications — exact Liquid file names, line numbers, before/after code snippets
  • Core Web Vitals baseline — Lighthouse lab data (5 pages × 2 devices) plus CrUX field data screenshots
  • Rich Results Test validation — Google’s own validation showing zero collection page rich results
  • Schema implementation templates — ready-to-paste JSON-LD for CollectionPage, ItemList, and BreadcrumbList

Key Takeaway

Technical SEO on Shopify is a code problem. Every issue — broken H1s, duplicate URLs, missing schema, CWV failures — traces back to specific Liquid template code. A crawl-first approach that maps every URL before touching any code ensures nothing gets missed, and file-level code specifications mean fixes can be implemented precisely without guesswork.


Seeing similar technical SEO issues on your Shopify store? Book a free strategy call and I’ll run a quick diagnostic on your site’s indexation, heading hierarchy, and structured data.

Frequently Asked Questions

How do you fix duplicate product URLs on Shopify?

Shopify generates collection-scoped product URLs like /collections/rings/products/diamond-ring alongside the canonical /products/diamond-ring. The fix involves modifying the product card Liquid snippet to replace {{ product.url | within: collection }} with /products/{{ product.handle }}, adding noindex meta tags to filtered and sorted collection URLs, and ensuring canonical tags point to the clean product path. In Enea Studio's case, this eliminated 25,285 duplicate internal links across 191 collection pages.

What causes multiple H1 tags on Shopify stores?

Most Shopify themes render the site logo as an H1 on every page, when it should only be H1 on the homepage and a div or span elsewhere. Additional H1 issues come from announcement bars, duplicate section titles, and FAQ content blocks using incorrect heading levels. The fix is a conditional Liquid check: {% if template == 'index' %}<h1>{% else %}<div>{% endif %} in the header section, plus demoting duplicate heading elements in collection and page templates.

How do you add structured data to Shopify collection pages?

Shopify collection pages typically lack CollectionPage, ItemList, and BreadcrumbList schema. The implementation involves injecting JSON-LD structured data in the collection template using Liquid loops to generate an ItemList with each product's name, URL, image, and position. BreadcrumbList schema is added separately to provide Google with the navigation hierarchy from homepage to collection to product.

What causes desktop Core Web Vitals to fail on Shopify?

Common causes include layout shifts from images without explicit width/height attributes (CLS), render-blocking CSS and JavaScript (LCP), excessive third-party scripts like Klarna widgets and review apps (TBT/INP), and unoptimized font loading. On Enea Studio, desktop CWV failed specifically on LCP (2.6s vs 2.5s threshold) and CLS (0.12 vs 0.1 threshold) while mobile passed all metrics.

How do you audit a Shopify store for technical SEO issues?

A systematic technical SEO audit covers crawl analysis (sitemap vs indexed URLs), canonical URL mapping, H1 heading hierarchy across all templates, duplicate content from filters and collection-scoped URLs, structured data validation via Rich Results Test, Core Web Vitals baseline using CrUX field data and Lighthouse lab data, and theme code inspection to map every issue to a specific Liquid file and line number for implementation.