Theme.liquid-Optimierungen für Shopify SEO

Vadim Kravcenko
Vadim Kravcenko
· 9 min read

TL;DR

Deine Shopify-Datei theme.liquid steuert den gesamten <head>-Bereich jeder Seite — und damit auch, wie Suchmaschinen deinen kompletten Shop sehen. Ich habe 10 Optimierungen zusammengestellt, mit denen du ein Dawn-Theme von einem Lighthouse-Score von 65 auf 90+ bringen kannst. Jede davon enthält direkt einsetzbaren Liquid-Code, eine Erklärung, was er macht, und woran es scheitern kann. Gesamtaufwand für die Umsetzung: 2-4 Stunden.

Ich habe über SEOJuice Hunderte Shopify-Stores auditiert. Das Muster ist fast immer identisch: Der Shop-Betreiber hat ein Theme gewählt, die Farben angepasst, Produkte hochgeladen — und theme.liquid nie wieder angefasst. Währenddessen rankt der Konkurrent mit denselben Produkten und halb so vielen Backlinks besser, weil sein Entwickler einen Nachmittag in die Optimierung des Theme-Codes investiert hat.

Das Shopify-Dawn-Theme erreicht out of the box 92 bei PageSpeed — das schnellste kostenlose Shopify-Theme, das es aktuell gibt. Aber „out of the box“ heißt eben auch: Es bleiben noch etliche SEO-Gewinne liegen. Fehlendes Schema-Markup. Nicht optimiertes Font-Loading. Keine Preconnect-Hints. Unvollständige OG-Tags.

Dieser Guide behebt genau das. Und der nächste Abschnitt — Canonical Tags — ist der Teil, von dem ich mir wünsche, dass ihn wirklich jeder Shopify-Store-Besitzer versteht, bevor er bei mir landet und fragt, warum Google die falsche Version seiner Produktseiten indexiert.

Was ist theme.liquid?

Falls du kein Entwickler bist, hier die 30-Sekunden-Version: theme.liquid ist die zentrale Template-Datei, die jede einzelne Seite in deinem Shopify-Store umschließt. Sie definiert die Struktur von <html>, <head> und <body>. Jede Produktseite, Kategorieseite, jeder Blogbeitrag und die Startseite erben von dieser Datei.

Wenn du etwas zum <head>-Bereich von theme.liquid hinzufügst, erscheint es auf jeder Seite. Genau deshalb ist es der perfekte Ort für:

  • Meta-Tags, die shopweit gelten (OG-Defaults, Twitter Cards)
  • Schema-Markup für dein Unternehmen
  • Resource Hints (preload, preconnect) für bessere Performance
  • Canonical-Tag-Logik
  • Optimierung des Font-Loadings

So bearbeitest du die Datei: Shopify Admin → Online Store → Themes → Edit Code → Layout → theme.liquid

Wichtigste Erkenntnis

Dupliziere dein Theme immer, bevor du etwas änderst. Geh zu Online Store → Themes → Actions → Duplicate. Arbeite auf der Kopie. Wenn etwas kaputtgeht, kannst du sofort zurückwechseln.

Schnelle Referenztabelle

Hier siehst du, was wir umsetzen, wo es hingehört und was es dir bringt:

Optimierung Wo einfügen SEO-Effekt Schwierigkeit
Canonical Tags theme.liquid <head> Hoch — verhindert Duplicate Content Einfach
Organization Schema theme.liquid <head> Mittel — Brand Knowledge Panel Einfach
Product Schema product.liquid oder Section Hoch — Rich Results in den SERPs Mittel
Breadcrumb Schema Snippet + Template Mittel — Breadcrumbs in den SERPs Mittel
Font Preloading theme.liquid <head> Hoch — bessere LCP Einfach
Critical CSS inline einbinden theme.liquid <head> Hoch — beseitigt Render-Blocking Schwer
Nicht kritisches JS defer laden theme.liquid Ende von <body> Hoch — schnelleres DOM-Parsing Mittel
OG/Twitter Meta Tags theme.liquid <head> Mittel — Social Sharing Einfach
Hreflang Tags theme.liquid <head> Hoch — internationales SEO Mittel
Lazy Loading für Bilder Templates/Sections Mittel — reduziert die initiale Ladegröße Einfach

1. Canonical Tags

Shopify erzeugt viele doppelte URLs. Ein Produkt kann über /products/widget und /collections/sale/products/widget erreichbar sein — dasselbe Produkt, zwei URLs. Ohne Canonical Tag sieht Google Duplicate Content und indexiert im Zweifel die falsche Version. Ich habe Stores gesehen, bei denen Google für jedes einzelne Produkt die URL mit Collection-Pfad indexiert hat. Das Ergebnis: Die „echten“ Produkt-URLs hatten praktisch keine Autorität.

Dawn bringt bereits eine grundlegende Canonical-Logik mit, aber du solltest sie prüfen und bei Bedarf verbessern. Füge das in den <head>-Bereich deiner theme.liquid ein:

{%- liquid
  assign canonical_url = canonical_url | default: request.origin | append: request.path
-%}

<link rel="canonical" href="{{ canonical_url }}">

{%- if paginate and paginate.pages > 1 -%}
  {%- if paginate.previous -%}
    <link rel="prev" href="{{ paginate.previous.url }}">
  {%- endif -%}
  {%- if paginate.next -%}
    <link rel="next" href="{{ paginate.next.url }}">
  {%- endif -%}
{%- endif -%}

Das macht drei Dinge: Es setzt die Canonical-URL über Shopifys eingebaute Variable canonical_url (die für Produkte die korrekte URL auflöst, egal wie sie aufgerufen werden), ergänzt rel="prev"/rel="next" für paginierte Collections und nutzt als Fallback Request-Origin + Pfad, falls canonical_url nicht verfügbar ist.

Wichtigste Erkenntnis

Prüfe zuerst, ob dein Theme nicht bereits ein Canonical Tag ausgibt, bevor du das hier einfügst. Doppelte Canonical Tags verwirren Suchmaschinen mehr, als gar keines zu haben. Suche in deiner theme.liquid zuerst nach „canonical“.

2. Organization Schema (JSON-LD)

Organization Schema sagt Google, wer du bist — dein Unternehmensname, Logo, Social-Profile und Kontaktdaten. Genau das speist das Knowledge Panel, das erscheint, wenn jemand nach deinem Brandnamen sucht. Ich habe erlebt, wie Stores innerhalb von drei Wochen von gar keinem Knowledge Panel zu einem vollständig befüllten Panel gekommen sind, nachdem dieses Markup sauber eingebaut wurde.

Füge das innerhalb von <head> in theme.liquid ein. Es läuft auf jeder Seite — und genau das will Google:

<script type="application/ld+json">
{
  "@context": "https://schema.org",
  "@type": "Organization",
  "name": {{ shop.name | json }},
  "url": "{{ shop.url }}",
  {%- if shop.brand.logo -%}
  "logo": {{ shop.brand.logo | image_url: width: 600 | json }},
  {%- endif -%}
  "description": {{ shop.description | json }},
  "address": {
    "@type": "PostalAddress",
    "addressCountry": {{ shop.address.country | json }}
  }
  {%- if shop.brand.social_links.size > 0 -%}
  ,"sameAs": [
    {%- for link in shop.brand.social_links -%}
      {{ link | json }}{%- unless forloop.last -%},{%- endunless -%}
    {%- endfor -%}
  ]
  {%- endif -%}
}
</script>

Wichtig: Ich nutze hier den Filter | json statt | escape. Das ist entscheidend — json escaped Strings korrekt für den JSON-LD-Kontext, inklusive Anführungszeichen. escape in JSON-LD zu verwenden, ist ein klassischer Fehler, der ungültiges Markup erzeugt. Ich habe genau dieses Problem bei mindestens einem Dutzend Kunden-Stores debuggt — das Schema validiert im Google-Testtool, scheitert aber still und leise live, weil das Escaping falsch ist.

„JSON-LD ist Googles bevorzugtes Format für strukturierte Daten. Wenn du Schema-Markup in Shopify implementierst, wähle immer JSON-LD statt Microdata — es ist sauberer, leichter zu debuggen und vermischt sich nicht mit deinem Präsentations-HTML.“

— Greg Bernhardt, Senior SEO Strategist bei Shopify (Quelle)

3. Product Schema

Product Schema sorgt für die Rich Results in Google — Sternebewertungen, Preis, Verfügbarkeitsstatus. Dawn enthält bereits ein grundlegendes Product Schema, aber oft ist es unvollständig. Hier ist eine umfassendere Version, die ich durch Tests in Dutzenden Stores verfeinert habe.

Erstelle ein neues Snippet: snippets/json-ld-product.liquid

{%- if product -%}
<script type="application/ld+json">
{
  "@context": "https://schema.org",
  "@type": "Product",
  "name": {{ product.title | json }},
  "url": "{{ shop.url }}{{ product.url }}",
  "description": {{ product.description | strip_html | truncate: 500 | json }},
  {%- if product.featured_image -%}
  "image": [
    {{ product.featured_image | image_url: width: 1200 | json }}
    {%- for image in product.images offset: 1 limit: 4 -%}
    ,{{ image | image_url: width: 1200 | json }}
    {%- endfor -%}
  ],
  {%- endif -%}
  "brand": {
    "@type": "Brand",
    "name": {{ product.vendor | json }}
  },
  {%- if product.selected_or_first_available_variant.sku != blank -%}
  "sku": {{ product.selected_or_first_available_variant.sku | json }},
  {%- endif -%}
  {%- if product.selected_or_first_available_variant.barcode != blank -%}
  "gtin": {{ product.selected_or_first_available_variant.barcode | json }},
  {%- endif -%}
  "offers": {
    "@type": "AggregateOffer",
    "priceCurrency": {{ cart.currency.iso_code | json }},
    "lowPrice": {{ product.price_min | money_without_currency | json }},
    "highPrice": {{ product.price_max | money_without_currency | json }},
    "offerCount": {{ product.variants.size }},
    "availability": "https://schema.org/{% if product.available %}InStock{% else %}OutOfStock{% endif %}",
    "url": "{{ shop.url }}{{ product.url }}"
  }
  {%- if product.metafields.reviews.rating.value != blank -%}
  ,"aggregateRating": {
    "@type": "AggregateRating",
    "ratingValue": {{ product.metafields.reviews.rating.value | json }},
    "reviewCount": {{ product.metafields.reviews.rating_count.value | json }}
  }
  {%- endif -%}
}
</script>
{%- endif -%}

Dann bindest du es in deinem Produkt-Template ein (meistens sections/main-product.liquid oder templates/product.liquid):

{% render 'json-ld-product' %}

Wichtige Details: Ich verwende AggregateOffer statt einzelner Offer-Objekte, weil das bei Produkten mit mehreren Varianten sauberer ist. Die Bewertungsfelder greifen auf Shopifys native Produktbewertungs-Metafields zu — wenn du eine Drittanbieter-Review-App nutzt, musst du den Metafield-Namespace anpassen. (Judge.me und Loox verwenden unterschiedliche Namespaces — also bitte nicht blind copy-pasten, sondern vorher in die Doku schauen.)

4. Breadcrumb Schema

Breadcrumbs helfen Google, die Hierarchie deiner Website zu verstehen, und können direkt in den Suchergebnissen erscheinen, statt der rohen URL. Das ist besonders wertvoll für Stores mit tieferen Kategoriestrukturen. In einem Store, den ich auditiert habe, änderte sich die Darstellung in der SERP von example.com › collections › summer-2025 › products › red-dress zu einem sauberen Home > Summer Collection > Red Dress — und die Klickrate stieg messbar.

Erstelle snippets/json-ld-breadcrumbs.liquid:

{%- assign breadcrumb_list = "" | split: "" -%}

{%- if template.name == 'product' -%}
  {%- if product.collections.size > 0 -%}
    {%- assign primary_collection = product.collections | first -%}
    {%- capture crumb_collection -%}{"name":{{ primary_collection.title | json }},"url":"{{ shop.url }}{{ primary_collection.url }}"}{%- endcapture -%}
    {%- assign breadcrumb_list = breadcrumb_list | push: crumb_collection -%}
  {%- endif -%}
  {%- capture crumb_product -%}{"name":{{ product.title | json }},"url":"{{ shop.url }}{{ product.url }}"}{%- endcapture -%}
  {%- assign breadcrumb_list = breadcrumb_list | push: crumb_product -%}

{%- elsif template.name == 'collection' -%}
  {%- capture crumb_coll -%}{"name":{{ collection.title | json }},"url":"{{ shop.url }}{{ collection.url }}"}{%- endcapture -%}
  {%- assign breadcrumb_list = breadcrumb_list | push: crumb_coll -%}

{%- elsif template.name == 'article' -%}
  {%- capture crumb_blog -%}{"name":{{ blog.title | json }},"url":"{{ shop.url }}{{ blog.url }}"}{%- endcapture -%}
  {%- assign breadcrumb_list = breadcrumb_list | push: crumb_blog -%}
  {%- capture crumb_article -%}{"name":{{ article.title | json }},"url":"{{ shop.url }}{{ article.url }}"}{%- endcapture -%}
  {%- assign breadcrumb_list = breadcrumb_list | push: crumb_article -%}
{%- endif -%}

{%- if breadcrumb_list.size > 0 -%}
<script type="application/ld+json">
{
  "@context": "https://schema.org",
  "@type": "BreadcrumbList",
  "itemListElement": [
    {
      "@type": "ListItem",
      "position": 1,
      "name": "Home",
      "item": "{{ shop.url }}"
    }
    {%- for crumb_json in breadcrumb_list -%}
    ,{
      "@type": "ListItem",
      "position": {{ forloop.index | plus: 1 }},
      "name": {{ crumb_json | split: '"name":' | last | split: ',"url"' | first }},
      "item": {{ crumb_json | split: '"url":' | last | split: '}' | first }}
    }
    {%- endfor -%}
  ]
}
</script>
{%- endif -%}

Füge {% render 'json-ld-breadcrumbs' %} in deiner theme.liquid direkt vor </head> ein. Das Snippet erkennt den Seitentyp automatisch und baut die passende Breadcrumb-Kette.

5. Font Preloading

Das hier ist die Änderung, die in unseren Test-Stores den größten Unterschied gemacht hat. Font Preloading allein kann locker eine volle Sekunde bei der Time-to-Text sparen — und im E-Commerce korreliert genau diese Sekunde direkt mit der Bounce Rate. Shopifys eigenes Engineering-Team hat den Effekt sogar auf shopify.com selbst gezeigt:

„Mit preload können Frontend-Entwickler dem Browser sagen, dass er die benötigten Font-Dateien gleichzeitig mit den CSS-Dateien herunterladen soll. Nur 17 Zeilen Code haben die Zeit bis zur Textanzeige auf shopify.com um 50% reduziert.“

— Colin Bendell, Shopify Engineering (Quelle)

Das Problem: Browser können Fonts erst herunterladen, nachdem sie sowohl das HTML als auch das referenzierende CSS geparst haben. Das sind zwei aufeinanderfolgende Round Trips, bevor überhaupt Text erscheint. Preloading sagt dem Browser: „Lad diese Font-Datei jetzt schon, du wirst sie gleich brauchen.“

Füge das möglichst weit oben im <head> deiner theme.liquid ein, vor deinen Stylesheet-Tags:

{%- comment -%}
  Preload your primary heading and body fonts.
  Check your theme's font settings to get the correct filenames.
  Use font-display: swap in your CSS to prevent FOIT (Flash of Invisible Text).
{%- endcomment -%}

{%- if settings.type_header_font != blank -%}
  <link rel="preload"
        href="{{ settings.type_header_font | font_url }}"
        as="font"
        type="font/woff2"
        crossorigin>
{%- endif -%}

{%- if settings.type_body_font != blank -%}
  <link rel="preload"
        href="{{ settings.type_body_font | font_url }}"
        as="font"
        type="font/woff2"
        crossorigin>
{%- endif -%}

{%- comment -%} Preconnect to Shopify's CDN for faster asset loading {%- endcomment -%}
<link rel="preconnect" href="https://cdn.shopify.com" crossorigin>
<link rel="preconnect" href="https://fonts.shopifycdn.com" crossorigin>

Das Attribut crossorigin ist bei Font-Preloads Pflicht — ohne dieses Attribut lädt der Browser die Font-Datei doppelt herunter. Ich habe genau diesen Fehler in Live-Stores gesehen, bei denen allein das Entfernen des doppelten Downloads die LCP um 400ms verbessert hat. Die Preconnect-Hints sparen zusätzlich etwa 100ms, weil die TCP- + TLS-Verbindung zum Shopify-CDN frühzeitig aufgebaut wird.

Wichtigste Erkenntnis

Preloade nur Fonts, die du above the fold wirklich nutzt. Jeder Preload konkurriert um Bandbreite. Wenn du fünf Fonts preloadest, hast du den Sinn der Sache ausgehebelt — der Browser lädt dann alle fünf parallel und bremst alles andere aus.

6. Critical CSS inline einbinden

Render-blocking CSS ist der Hauptgrund, warum Shopify-Stores in Lighthouse schlecht abschneiden. Der Browser kann nichts rendern, bis er dein komplettes Stylesheet heruntergeladen und geparst hat — selbst dann nicht, wenn ein Teil des CSS nur den Footer betrifft, der 3,000px unterhalb des sichtbaren Bereichs liegt. Das ist die schwierigste Optimierung auf dieser Liste, aber sie liefert in Audits konstant die größten Lighthouse-Gewinne, die ich gesehen habe.

Die Lösung: Binde das CSS für den above-the-fold-Bereich direkt in <head> inline ein und lade den Rest asynchron.

{%- comment -%}
  Step 1: Inline critical CSS directly in the head.
  Generate your critical CSS using a tool like our
  Critical CSS Generator at /tools/critical-css-generator/
  Then paste it into a snippet: snippets/critical-css.liquid
{%- endcomment -%}

<style>
  {% render 'critical-css' %}
</style>

{%- comment -%}
  Step 2: Load full stylesheet asynchronously using the media trick.
  The browser treats media="print" as non-render-blocking,
  then onload switches it to media="all" so styles apply.
{%- endcomment -%}

<link rel="stylesheet"
      href="{{ 'base.css' | asset_url }}"
      media="print"
      onload="this.media='all'">

<noscript>
  <link rel="stylesheet" href="{{ 'base.css' | asset_url }}">
</noscript>

Alternativ stellt Shopify den Liquid-Filter inline_asset_content bereit, mit dem du CSS direkt aus deinen Asset-Dateien inline einbetten kannst. Dann brauchst du kein separates Snippet:

<style>{{ 'critical.css' | asset_url | inline_asset_content }}</style>

Critical CSS korrekt zu erzeugen, bedeutet, jedes Template zu analysieren. Ich empfehle dafür ein Critical-CSS-Generator-Tool — es extrahiert exakt die CSS-Regeln, die für den sichtbaren Bereich jedes Seitentyps nötig sind. Wenn du das falsch machst (zum Beispiel eine Regel für deine Hero-Section vergisst), bekommst du einen Flash of Unstyled Content — und das ist schlimmer als gar keine Optimierung.

7. Nicht kritisches JavaScript defer laden

Parser-blocking Scripts sind nach render-blocking CSS der zweitgrößte Performance-Killer. Shopifys offizielle Empfehlung ist ziemlich klar: Dein minifiziertes JS-Bundle sollte 16KB oder weniger haben, und alles andere sollte deferred geladen werden.

Suche in deiner theme.liquid nach den <script>-Tags und ergänze defer:

{%- comment -%}
  WRONG — blocks DOM parsing:
  {{ 'theme.js' | asset_url | script_tag }}

  RIGHT — deferred loading:
{%- endcomment -%}

<script src="{{ 'theme.js' | asset_url }}" defer></script>

{%- comment -%}
  For third-party scripts (analytics, chat widgets, etc.),
  load them after the page is interactive:
{%- endcomment -%}

<script>
  // Load non-essential scripts after user interaction
  function loadDeferredScripts() {
    // Example: chat widget, analytics, etc.
    var scripts = [
      '{{ "deferred-features.js" | asset_url }}'
    ];
    scripts.forEach(function(src) {
      var s = document.createElement('script');
      s.src = src;
      s.defer = true;
      document.body.appendChild(s);
    });
  }

  // Trigger on first user interaction
  ['click', 'scroll', 'keydown', 'touchstart'].forEach(function(evt) {
    window.addEventListener(evt, function handler() {
      loadDeferredScripts();
      ['click', 'scroll', 'keydown', 'touchstart'].forEach(function(e) {
        window.removeEventListener(e, handler);
      });
    }, { once: true, passive: true });
  });
</script>

Das Interaktions-basierte Laden ist aggressiv, aber effektiv. Chat-Widgets, Review-Popups und ähnliche Features müssen nicht geladen werden, bevor der Nutzer überhaupt irgendetwas auf der Seite gemacht hat. In einem Store, den wir getestet haben, hat dieses Muster 340ms bei Time to Interactive eingespart — und genau das war der Unterschied zwischen einem „needs improvement“ und einem „good“ INP-Score in den CrUX-Daten.

8. OG/Twitter Meta Tags

Wenn jemand dein Produkt auf Facebook, Twitter oder LinkedIn teilt, steuern OG-Tags (Open Graph), was in der Vorschau erscheint. Ohne diese Tags raten die Plattformen — und sie raten oft spektakulär schlecht. Ich habe Produkt-Sharings gesehen, bei denen das Favicon des Stores als Bild auftauchte und der Seitentitel der Checkout-Seite als Beschreibung verwendet wurde.

Dawn enthält ein Snippet namens social-meta-tags.liquid, aber das ist oft sehr minimal. Hier ist eine umfassendere Version. Erstelle oder aktualisiere snippets/social-meta-tags.liquid:

{%- liquid
  assign og_title = page_title | default: shop.name
  assign og_description = page_description | default: shop.description | strip_html | truncate: 200
  assign og_url = canonical_url | default: request.origin | append: request.path
  assign og_type = 'website'
  assign og_image = ''
-%}

{%- if template.name == 'product' -%}
  {%- assign og_type = 'product' -%}
  {%- if product.featured_image -%}
    {%- assign og_image = product.featured_image | image_url: width: 1200, height: 630 -%}
  {%- endif -%}
{%- elsif template.name == 'article' -%}
  {%- assign og_type = 'article' -%}
  {%- if article.image -%}
    {%- assign og_image = article.image | image_url: width: 1200, height: 630 -%}
  {%- endif -%}
{%- elsif template.name == 'collection' -%}
  {%- if collection.image -%}
    {%- assign og_image = collection.image | image_url: width: 1200, height: 630 -%}
  {%- endif -%}
{%- endif -%}

{%- if og_image == blank and shop.brand.cover_image -%}
  {%- assign og_image = shop.brand.cover_image | image_url: width: 1200, height: 630 -%}
{%- endif -%}

<!-- Open Graph -->
<meta property="og:site_name" content="{{ shop.name }}">
<meta property="og:title" content="{{ og_title }}">
<meta property="og:description" content="{{ og_description }}">
<meta property="og:url" content="{{ og_url }}">
<meta property="og:type" content="{{ og_type }}">
{%- if og_image != blank -%}
<meta property="og:image" content="{{ og_image }}">
<meta property="og:image:width" content="1200">
<meta property="og:image:height" content="630">
{%- endif -%}

{%- if template.name == 'product' -%}
<meta property="product:price:amount" content="{{ product.price | money_without_currency }}">
<meta property="product:price:currency" content="{{ cart.currency.iso_code }}">
{%- endif -%}

<!-- Twitter Card -->
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:title" content="{{ og_title }}">
<meta name="twitter:description" content="{{ og_description }}">
{%- if og_image != blank -%}
<meta name="twitter:image" content="{{ og_image }}">
{%- endif -%}

Binde es in theme.liquid mit {% render 'social-meta-tags' %} innerhalb von <head> ein. Die Bildmaße 1200x630 sind sowohl für Facebook als auch für das Large-Card-Format von Twitter/X optimiert.

9. Hreflang Tags für internationale Stores

Wenn du in mehreren Ländern oder Sprachen verkaufst, sagen hreflang-Tags Google, welche Version einer Seite welcher Zielgruppe angezeigt werden soll. Ohne sie kann es passieren, dass dein französischer Store in deutschen Suchergebnissen auftaucht — und ja, ich habe genau das bei einem Kunden gesehen, der monatelang Support-Tickets in der falschen Sprache bekam, bevor die Ursache klar war.

Shopify Markets erzeugt hreflang-Tags für internationale Domains und Subfolder automatisch. Wenn du aber mehr Kontrolle brauchst — oder eine Übersetzungs-App verwendest — füge das in den <head>-Bereich deiner theme.liquid ein:

{%- if request.locale and localization.available_languages.size > 1 -%}
  {%- for locale in localization.available_languages -%}
    {%- if locale.primary -%}
      {%- assign locale_root = shop.url -%}
    {%- else -%}
      {%- assign locale_root = shop.url | append: '/' | append: locale.iso_code -%}
    {%- endif -%}

    <link rel="alternate"
          hreflang="{{ locale.iso_code }}"
          href="{{ locale_root }}{{ request.path }}">
  {%- endfor -%}

  {%- comment -%} x-default points to your primary language {%- endcomment -%}
  <link rel="alternate"
        hreflang="x-default"
        href="{{ shop.url }}{{ request.path }}">
{%- endif -%}

Das x-default-Tag ist kritisch — es sagt Google, welche Version angezeigt werden soll, wenn keine der angegebenen Sprachen zur Nutzerpräferenz passt. Zeige damit immer auf deinen primären Markt.

10. Lazy Loading für Bilder

Natives Lazy Loading wird inzwischen von 92% aller Browser unterstützt. Shopifys Liquid-Filter image_tag kümmert sich automatisch darum — Bilder nach den ersten drei Sections erhalten standardmäßig loading="lazy".

Du musst aber sicherstellen, dass du Bilder above the fold nicht lazy lädst. Das ist ein häufiger Fehler, der LCP sogar verschlechtert. Ich habe letzten Monat einen Store auditiert, der loading="lazy" auf seinem Hero-Banner gesetzt hatte — das hat 800ms zur LCP hinzugefügt, weil der Browser das wichtigste Bild der Seite herunterpriorisiert hat:

{%- comment -%}
  Hero image — above the fold, NEVER lazy load.
  Use loading: 'eager' and fetchpriority: 'high' to prioritize it.
{%- endcomment -%}

{{ section.settings.hero_image
  | image_url: width: 1920
  | image_tag:
    loading: 'eager',
    fetchpriority: 'high',
    sizes: '100vw',
    widths: '375, 750, 1100, 1500, 1920'
}}

{%- comment -%}
  Product images below the fold — let Shopify's default lazy loading work.
  Don't set loading attribute; Shopify handles it via section.index.
{%- endcomment -%}

{{ product.featured_image
  | image_url: width: 800
  | image_tag:
    sizes: '(min-width: 750px) 50vw, 100vw',
    widths: '375, 750, 800'
}}

Das Attribut fetchpriority: 'high' für dein LCP-Bild ist eine neuere Ergänzung, die dem Browser sagt, dass dieses Bild vor anderen Ressourcen priorisiert werden soll. Zusammen mit dem bewussten Nicht-Lazy-Loading ist das die einzelne Maßnahme mit dem größten Hebel für deinen LCP-Score.

Deine Änderungen debuggen

Shopify Web Performance Dashboard mit Core Web Vitals-Scores im Zeitverlauf und markierten Ereignissen
Shopifys integriertes Web Performance Dashboard zeigt, wie sich Theme-Änderungen im Zeitverlauf auf die Core Web Vitals auswirken. Quelle: Shopify Performance Blog

Du hast die Änderungen eingebaut. Jetzt musst du prüfen, ob sie tatsächlich funktionieren. (Ich habe Entwickler erlebt, die diesen Schritt übersprungen haben und drei Monate später festgestellt haben, dass ihr Schema die ganze Zeit ungültig war.)

Schritt 1: Strukturierte Daten validieren. Geh zum Rich Results Test von Google und gib deine Produkt-URL ein. Jeder Schema-Typ, den du hinzugefügt hast, sollte mit einem grünen Haken erscheinen. Häufige Fehler: fehlende Kommas in JSON-LD-Arrays, nicht escapte Anführungszeichen in Produktbeschreibungen und ungültige Bild-URLs.

Schritt 2: Canonical Tags prüfen. Öffne den Seitenquelltext (Ctrl+U) und suche nach „canonical“. Du solltest genau ein Canonical Tag pro Seite sehen. Wenn du zwei siehst, hat dein Theme bereits eines ausgegeben — entferne das Duplikat.

Schritt 3: Lighthouse ausführen. Öffne Chrome DevTools → Lighthouse → setze Häkchen bei Performance und SEO → Generate Report. Vergleiche deine Scores vor und nach den Änderungen. Mach Screenshots — du wirst den Vorher/Nachher-Vergleich haben wollen, wenn ein Kunde fragt, was du eigentlich getan hast.

Schritt 4: Social Previews testen. Nutze den Sharing Debugger von Facebook und den Card Validator von Twitter, um zu prüfen, wie deine Produktseiten beim Teilen aussehen.

Typische Fehler, die dir später um die Ohren fliegen:

  • Ungültiges JSON-LD — nutze jsonlint.com, um dein JSON vor dem Deployment zu validieren. Liquid-Output kann trailing commas oder fehlende Klammern erzeugen.
  • Doppelte Tags — wenn dein Theme bereits OG-Tags enthält, erzeugt zusätzliches Einfügen Duplikate. Entferne die Originale oder überschreibe sie sauber.
  • Zu viele Ressourcen preladen — Browser handhaben pro Domain nur 6 gleichzeitige Verbindungen. Wenn du 8 Dinge preloadest, schadest du der Performance statt ihr zu helfen.
  • Falsche Font-Datei im Preload — wenn du eine woff2-Font preloadest, dein CSS aber eine woff-Font referenziert, lädt der Browser beide. Das Format muss exakt übereinstimmen.

Performance-Auswirkung

Google Lighthouse-Audit-Ergebnisse mit Performance-, Accessibility-, Best-Practices- und SEO-Scores für einen Shopify-Store
Google-Lighthouse-Scores für einen Shopify-Store. Theme-Liquid-Optimierungen wie deferred JavaScript und vorgeladene kritische Ressourcen verbessern diese Werte direkt. Quelle: Shopify Enterprise Blog

Das kannst du auf Basis meiner Audits bei Dawn-basierten Stores realistisch von den einzelnen Optimierungen erwarten:

Optimierung Erwartete LCP-Änderung Erwartete CLS-Änderung Lighthouse-Punkte
Font Preloading -300ms to -1.2s Keine Änderung +3 to +8
Critical CSS inline -200ms to -800ms -0.02 to -0.05 +5 to +15
Nicht kritisches JS defer laden -100ms to -500ms Keine Änderung +3 to +10
Lazy Loading für Bilder (korrekt umgesetzt) -100ms to -400ms Leichte Verbesserung +2 to +5
Schema-Markup (alle Typen) Keine Änderung Keine Änderung +2 to +5 (SEO-Score)
Canonical + hreflang Keine Änderung Keine Änderung +1 to +3 (SEO-Score)
Kombinierte Gesamtwirkung -0.7s to -2.9s -0.02 to -0.05 +15 to +35

Diese Zahlen variieren stark je nach Ausgangslage. Ein Store, der bereits 85+ in Lighthouse erreicht, wird kleinere Gewinne sehen als einer mit 55. Font Preloading und Critical CSS liefern aber konstant die größten Verbesserungen — wenn du wenig Zeit hast, fang mit genau diesen beiden an.

FAQ

Überstehen diese Änderungen ein Theme-Update?

Wenn du den Theme-Code direkt bearbeitest — nein. Theme-Updates überschreiben deine Änderungen. Deshalb empfehle ich, wann immer möglich mit Snippets zu arbeiten (json-ld-product.liquid, social-meta-tags.liquid usw.). Snippets überstehen manche Updates. Für maximale Sicherheit solltest du jede Änderung dokumentieren und nach Updates erneut einspielen — oder mit einem Version-Control-Workflow über Shopify CLI arbeiten.

Kann ich statt Code-Änderungen auch eine Shopify-App verwenden?

Ja, Apps wie JSON-LD for SEO und Smart SEO übernehmen Schema-Markup ohne Code-Anpassungen. Der Trade-off: Apps bringen JavaScript-Overhead mit (ironisch, ich weiß), kosten $5-20/Monat und geben dir weniger Kontrolle. Für Font Preloading und Critical CSS gibt es allerdings keinen echten Ersatz für Änderungen auf Code-Ebene.

Funktioniert das auch mit anderen Themes als Dawn?

Schema-Markup, OG-Tags und hreflang-Code funktionieren mit jedem Shopify-Theme. Die Syntax für Font Preloading kann abweichen — prüfe in der settings_schema.json deines Themes die korrekten Variablennamen für Fonts. Critical CSS ist definitionsgemäß themespezifisch. Die Liquid-Code-Muster selbst sind aber in allen Online Store 2.0-Themes universell.

Wie erkenne ich, ob mein Schema-Markup funktioniert?

Nutze für einzelne URLs den Rich Results Test von Google (search.google.com/test/rich-results). Für eine siteweite Validierung prüfst du Google Search Console → Enhancements. Google kann 2-4 Wochen brauchen, um neue strukturierte Daten zu verarbeiten und Ergebnisse im Enhancements-Report anzuzeigen.

Was ist der Unterschied zwischen defer und async bei Script-Tags?

defer lädt das Script parallel herunter, wartet mit der Ausführung aber, bis das HTML vollständig geparst ist. async lädt ebenfalls parallel, führt das Script aber sofort aus, sobald es bereit ist — und kann damit das HTML-Parsing unterbrechen. Für Theme-Scripts solltest du immer defer verwenden. async ist nur für wirklich unabhängige Scripts sinnvoll, etwa Analytics, die nicht mit dem DOM interagieren.

Wie geht’s weiter?

Diese 10 Optimierungen decken die Änderungen mit dem größten Hebel ab, die du an theme.liquid vornehmen kannst. Aber SEO ist mehr als Code. Deine Produktbeschreibungen, interne Verlinkung, Bild-Alt-Texte und Content-Strategie sind genauso wichtig.

Wenn du tiefer einsteigen willst: