← Back to Blog

Mega Analyzer goes Shopify-aware — six new detectors and one suppressor

Mega Analyzer goes Shopify-aware — six new detectors and one suppressor

I audited a Shopify storefront last week and the report drowned in false positives. The visually-hidden H1 next to the logo (Shopify's standard "logo-as-H1" pattern) tripped duplicate-H1 warnings. The pair of og:image tags — one HTTP, one HTTPS — looked like mixed content to the analyzer. The Trust dim said the store had no privacy or terms pages, even though both lived at /policies/privacy-policy and /policies/terms-of-service, where Shopify hosts them by default.

Meanwhile, the real defects went undetected. Every Product schema on the store said "brand":{"name":"examplehats.shop"} — the merchant's Vendor field was unfilled, so Shopify was using the bare domain. The first product in the products sitemap was out-of-stock at price $0.00, sending every Google referral to an empty landing page. Multiple product description and category fields emitted as empty strings ("") rather than being omitted, which Google Merchant Center quietly rejects.

So the Mega Analyzer got a Shopify-aware rule pack. Six new detectors fire only when the analyzer fingerprints a Shopify storefront, and one suppressor fixes a long-standing /policies/* false negative across the Trust dim.

How the platform fingerprint works

A site is classified as Shopify if any of the following hold:

const fingerprints = [
  /cdn\.shopify\.com/i,
  /shopify\.com\/s\//i,
  /Shopify\.theme/i,
  /data-shopify-/i,
  /<header-component\b/i,
  /<predictive-search\b/i,
  /\bShopify\.shop\b/i
];
isShopify = fingerprints.some(re => re.test(html))
  || /shopify/i.test(headers['powered-by'] || '')
  || /shopify/i.test(headers['x-shopify-stage'] || '');

The fingerprint is intentionally permissive — any one signal is enough. False positives here are cheap (the rule pack on a non-Shopify site contributes a couple of no-op findings); false negatives are expensive (the merchant doesn't see the Shopify-specific defects). Most of the false-positive risk is around Liquid-templated themes hosted elsewhere, but those still get flagged by the platform-default behaviors and the merchant-facing remediation paths (Shopify Admin links) still mostly work as a Liquid theme cheat-sheet.

The dimension itself is pushed conditionally into the analyzer's dims[] array — the same pattern dimFieldPerformance uses for CrUX data. On non-Shopify sites the dim isn't added, so the overall score average doesn't shift.

The six detectors

1. Vendor field = bare domain

The most common Shopify merchant misconfiguration. When a merchant doesn't fill the Vendor field on a product, Shopify falls back to using the store's domain. The resulting Product schema looks like this:

"brand": { "@type": "Brand", "name": "examplehats.shop" }

That's a Google Shopping eligibility killer. The Knowledge Graph reconciler treats the bare domain as a different entity from the brand the customer is searching for. The fix lives in Shopify Admin → Products → bulk-edit → Vendor.

The detector test:

const brandVal = brandValMatch[1];
const looksLikeDomain = /^[a-z0-9-]+(\.[a-z0-9-]+){1,}$/i.test(brandVal);
const matchesHost    = host && brandVal.toLowerCase() === host.toLowerCase();
if (looksLikeDomain || matchesHost) add('fail', ...);

Either condition fires the finding. Pure-domain values catch the bare-vendor case; matchesHost catches mixed-case variants (e.g. ExampleHats.Shop) that wouldn't match the strict-lowercase domain regex.

2. Out-of-stock product at $0 in the sitemap

The audit that motivated this detector found a product page returning availability: OutOfStock with price: "0.00", and that product was the first entry in sitemap_products_1.xml. So Google's crawler — which prioritises first-listed products in a sitemap during initial discovery — was sending users to an empty product page. The detector:

const isOos  = /availability"\s*:\s*"[^"]*OutOfStock"/i.test(offerStr);
const isZero = /"price"\s*:\s*"0(?:\.0+)?"/i.test(offerStr);
if (isOos && isZero) add('fail', ...);

Fix: either unpublish the product (Shopify Admin → set Status to Draft) or set up a 301 redirect to the parent collection (Admin → Online Store → Navigation → URL Redirects).

3. Empty description or category strings in Product schema

Shopify emits empty strings rather than omitting a field. Most validators (including schema.org's) accept "". Google Merchant Center does not. Detector:

const emptyDescription = /"description"\s*:\s*""/.test(productStr);
const emptyCategory    = /"category"\s*:\s*""/.test(productStr);

Fix path is in the Shopify admin, not the theme — the merchant has to actually fill the Description and Google Product Category fields.

4. Theme-color meta present but empty

<meta name="theme-color" content="">

Shopify's Studio theme emits the meta even when the merchant hasn't picked a color in Theme settings. Browsers default the mobile chrome to white, breaking branded Add-to-Home-Screen and undermining the visual brand. The fix is one click in Online Store → Themes → Customize → Theme settings → Colors.

5. Missing <link rel="icon">

Shopify only emits the favicon link when the merchant has uploaded a favicon in theme settings. If it isn't uploaded, the browser tab shows the default file-icon and /favicon.ico returns 404. The detector flags either the missing link OR the 404 on the file; the remediation is the same: upload a square PNG at Online Store → Themes → Customize → Theme settings → Favicon.

6. Duplicate H1, with Shopify "logo-as-H1" awareness

Shopify themes (including Dawn, Sense, Studio, and most third-party themes) put the brand name inside an <h1 class="visually-hidden"> next to the logo, so screen readers announce the brand without a visible H1 competing with the logo image. That pattern is fine. The defect is the second H1 — usually emitted by a re-used section like a newsletter banner or hero promo — which then duplicates on every page that includes that section.

The detector counts H1s. If one is visually-hidden and the other(s) are not, it explicitly tells the merchant: keep the hidden one, demote the visible one. If both H1s are visible, it flags the page generically (since neither is special).

The same logic landed in wcag-quick-checks.js's h1-present rule so the WCAG Quick Check and the Mega Analyzer agree on which H1 is the defect.

The one suppressor

dimTrust was checking for href="/privacy", href="/terms", and href="/cookie" to count policy links. Shopify stores never use those paths — every policy lives under /policies/*. The check now also accepts /policies/privacy-policy, /policies/terms-of-service, and /policies/cookie-policy:

const hasPrivacy = /href=["'](?:[^"']*\/)?(?:privacy(?:-policy)?|policies\/privacy(?:-policy)?)\b/i.test(html);
const hasTerms   = /href=["'](?:[^"']*\/)?(?:terms(?:-of-(?:service|use))?|policies\/terms(?:-of-(?:service|use))?)\b/i.test(html);
const hasCookies = /href=["'](?:[^"']*\/)?(?:cookies?(?:-policy)?|policies\/cookies?(?:-policy)?)\b/i.test(html);

The change is strictly more tolerant: any site that previously passed the check still passes. Shopify sites with proper policies in /policies/* stop false-positiving.

What the analyzer does NOT flag on Shopify

Several patterns that look like defects on a generic SEO checker are Shopify defaults and shouldn't be flagged at all. The analyzer doesn't currently emit warnings for any of these:

  • og:image HTTP paired with og:image:secure_url HTTPS (OG-spec compliant Shopify default — both Facebook and LinkedIn honor secure_url).
  • @context: http://schema.org (Shopify emits HTTP; Google + schema.org both accept).
  • HSTS max-age around 91 days (Shopify infrastructure default, not preload-eligible by design).
  • _shopify_y / _shopify_s / _shopify_essential cookies (cart/session necessary).
  • <meta http-equiv="X-UA-Compatible" content="IE=edge"> (legacy IE compat; Shopify still emits).
  • Relative @id on Product schema (/products/foo#product) — Shopify's reconciler handles relative IDs fine.
  • Multiple JSON-LD blocks where the @type scopes differ (Organization + Product is NOT page-type dilution; both blocks have distinct entity scope).

If a future audit shows one of these false-positiving, the suppressor list is the right place to land the fix.

Back-test results

Before deploying, the new code was back-tested against three live sites:

Fixture Platform dimShopify fires? Note
Shopify storefront (Studio theme v3.5.1) Shopify yes All six detectors fire on the appropriate page types; Vendor=domain + OOS+$0 + empty description all caught
jwatte.com Eleventy / Netlify no detectShopifyContext.isShopify === false; dim returns skip:true and is not pushed to dims[]; existing scores unchanged
Second non-Shopify Eleventy site (control) Eleventy / Netlify no Same — no new dim added

The non-Shopify sites' overall score, plus every individual dim score except dimTrust (which can only improve, never regress, thanks to the more-tolerant policy regex), is byte-for-byte identical to v2 output.

How to run it

The Mega Analyzer is at /tools/mega-analyzer/. Paste any URL — Shopify-aware checks are automatic. If the analyzer fingerprints Shopify, you get a new "Shopify storefront checks" dimension card alongside the existing eight (or nine, with CrUX). Each finding includes a 📖 Learn link back to this post.

The companion fix-generator pills for Shopify findings deep-link into /tools/schema-validator/ (for the brand=domain + empty-string bugs), /tools/sitemap-audit/ (for the OOS+$0 trap), and /tools/headings-outline/ (for the duplicate-H1 finding). The Audit pills are wired the same way.

Related reading

If you run a Shopify storefront, the audit-then-fix loop is: run the Mega Analyzer, work through the Shopify-storefront-checks card top to bottom, and most fixes will land in Shopify Admin — no theme code required. The harder ones (expanded Organization schema, BreadcrumbList on products, WebSite + SearchAction on the homepage) live in the theme's theme.liquid and need a developer; the schema snippets are in The $97 Launch chapter 6 if you want the copy-paste-ready versions.

This post is informational, not legal or e-commerce advice. Mentions of Shopify, Google Merchant Center, and other third parties are nominative fair use. No affiliation is implied.

← Back to Blog

Accessibility Options

Text Size
High Contrast
Reduce Motion
Reading Guide
Link Highlighting
Accessibility Statement

J.A. Watte is committed to ensuring digital accessibility for people with disabilities. This site conforms to WCAG 2.1 and 2.2 Level AA guidelines.

Measures Taken

  • Semantic HTML with proper heading hierarchy
  • ARIA labels and roles for interactive components
  • Color contrast ratios meeting WCAG AA (4.5:1)
  • Full keyboard navigation support
  • Skip navigation link
  • Visible focus indicators (3:1 contrast)
  • 44px minimum touch/click targets
  • Dark/light theme with system preference detection
  • Responsive design for all devices
  • Reduced motion support (CSS + toggle)
  • Text size customization (14px–20px)
  • Print stylesheet

Feedback

Contact: jwatte.com/contact

Full Accessibility StatementPrivacy Policy

Last updated: April 2026