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.
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>
BreadcrumbList Implementation
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
widthandheightattributes 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.