Shopware SEO 2026: Was der Shop wirklich ausliefert – gemessen an einem Live-Shop

Shopware SEO 2026: Was der Shop wirklich ausliefert – gemessen an einem Live-Shop

Shopware SEO wird meistens als Liste von Häkchen erklärt: SEO-URLs anpassen, Sitemap einreichen, Meta-Titel pflegen, fertig. Das Problem an dieser Darstellung ist nicht, dass sie falsch wäre – sie ist nur folgenlos. Sie sagt dir, was du einstellen sollst, aber nicht, was dein Shop danach tatsächlich an Google ausliefert. Und genau dort klafft die Lücke.

Für diesen Artikel haben wir deshalb nicht die Dokumentation zusammengefasst, sondern gemessen. Grundlage ist ein produktiver Shopware-6-Shop, den wir betreuen: Shopware 6.7.12.2, 15.562 SEO-URLs in der Datenbank, 2.604 Produkte, zehn Sales Channels, gewachsen seit 2021. Jede Zahl in diesem Artikel stammt aus einer Abfrage gegen diese Datenbank oder aus einem HTTP-Aufruf gegen den Live-Shop. Zusätzlich haben wir jede Aussage über „Shopware-Standardverhalten” an einer plugin-armen Shopware-Demoinstallation gegengeprüft – weil sich sonst nicht unterscheiden lässt, was Shopware macht und was ein gekauftes Plugin macht.

Das Ergebnis vorweg: Zwei der drei größten Funde waren an keiner Stelle als Fehler sichtbar. Kein rotes Feld im Admin, keine Warnung, kein Log-Eintrag. Sie fallen nur auf, wenn man das ausgelieferte HTML gegen die Konfiguration hält.

Visualisierung einer Datenbanktabelle, in der eine Zeile als aktueller Haupteintrag hervorgehoben ist, während zahlreiche blasse historische Zeilen dahinterstehen

Die Kurzfassung

Was wir geprüft habenErgebnis im Live-Shop
SEO-URLs gesamt15.562
davon kanonisch und aktiv9.212
Alt-URLs pro Produkt (Durchschnitt)5,36 – Maximum: 32
Alte URLs → leiten sie weiter?25 von 25 mit 301, ein einziger Hop
Als gelöscht markierte URLs12 von 12 mit 404
Kanonische URLs, deren Produkt fehlt38 von 775 (4,9 %) – liefern 404
Stehen diese 38 in der Sitemap?0 von 38 – die Sitemap ist sauber
Sitemap-Stichprobe20 von 20 mit 200
hreflang im HTML0 Tags – trotz Shops für AT und CH
hreflang_active in der Datenbank0 bei allen 10 Sales Channels
HTML-Größe Produktseite621 KB dekodiert / 84 KB übertragen
Standard-Shopware zum Vergleich194 KB dekodiert / 27 KB übertragen

Drei dieser Zeilen verdienen einen eigenen Abschnitt, weil sie jeweils ein Missverständnis auflösen, das in fast jedem Shopware-SEO-Artikel steht.

SEO-URLs in Shopware: Wie die Tabelle wirklich funktioniert

Shopware speichert sprechende URLs in der Tabelle seo_url. Jede Zeile verbindet eine lesbare Adresse (seo_path_info) mit einer internen Route (path_info). Interessant sind zwei Spalten, weil sie zusammen über 301, 404 und 200 entscheiden: is_canonical und is_deleted.

Hier die Verteilung im gemessenen Shop:

RouteZeilenkanonischals gelöscht markiert
frontend.detail.page (Produkte)13.9648.3476.433
frontend.navigation.page (Kategorien)1.117564484
Blog-Detailseiten (Plugin)4442720
frontend.landing.page372914

Auffällig ist das Verhältnis: Auf 2.604 Produkte kommen 13.964 Produkt-URL-Zeilen. Im Schnitt trägt jedes Produkt 5,36 Adressen mit sich, eines sogar 32. Das ist kein Fehler, sondern Absicht – Shopware behält alte Adressen, um sie weiterleiten zu können. Aber es erklärt, warum diese Tabelle in gewachsenen Shops die mit Abstand größte SEO-Datenmenge ist.

Die Falle, die uns selbst erwischt hat

Unsere erste Abfrage lautete sinngemäß „zähle alle nicht-kanonischen URLs” – also WHERE is_canonical = 0. Das Ergebnis: 0 Zeilen. Eine saubere, plausible Zahl, die bedeutet hätte: Es gibt gar keine Weiterleitungs-URLs.

Die Zahl war falsch, und zwar nicht knapp. Tatsächlich sind es 6.350. Der Grund steht im Shopware-Quelltext, in SeoUrlPersister.php:

$insert['is_canonical'] = ($seoUrl['isCanonical'] ?? true) ? 1 : null;

Shopware schreibt in diese Spalte entweder 1 oder NULL – niemals 0. Und in SQL ist NULL = 0 nicht falsch, sondern unbekannt, weshalb die Zeile aus dem Ergebnis fällt, ohne dass jemand einen Fehler sieht.

Das ist die eigentliche Lehre dieses Abschnitts, und sie gilt weit über Shopware hinaus: Eine Abfrage, die ein leeres Ergebnis liefert, sieht genauso aus wie eine Abfrage, die korrekt „nichts gefunden” meldet. Wer mit is_canonical = 0 arbeitet, bekommt bei jeder Prüfung eine beruhigende Null zurück. Richtig ist is_canonical IS NULL.

Was 301 von 404 unterscheidet

Nachdem die Zahlen stimmten, haben wir das Verhalten getestet statt es anzunehmen. Dafür haben wir zufällig 25 nicht-kanonische, nicht gelöschte Produkt-URLs des passenden Sales Channels gezogen und live abgerufen:

Ergebnis: 25 von 25 antworten mit 301 – und zwar mit genau einem Sprung direkt auf das Ziel, keine Ketten. Beispiel:

/Flauschtuecher-Bunt1-12er-Set/4464089104463
  → 301 → /flauschi-mikrofasertuecher-bunt-12tlg./324284  → 200

Danach dieselbe Prüfung mit 12 als gelöscht markierten URLs: 12 von 12 antworten mit 404.

Damit ist die Regel sauber belegt und nicht bloß behauptet:

  • is_deleted = 0 → alte URL leitet weiter (301)
  • is_deleted = 1 → alte URL ist tot (404)

Verantwortlich dafür ist die Einstellung core.seo.redirectToCanonicalUrl, die im gemessenen Shop auf true steht und im Core von CanonicalRedirectService.php ausgewertet wird.

Warum eine kurze Anfangsmessung in die Irre führte

Ehrlichkeitshalber: Unsere allererste Stichprobe bestand aus drei URLs, und die lieferten 3× 404. Daraus hätte man geschlossen, dass Shopware alte Adressen gar nicht weiterleitet – das genaue Gegenteil des korrekten Befunds.

Die Auflösung: Alle drei gehörten zu anderen Sales Channels, und für den getesteten Shop waren sie zusätzlich als gelöscht markiert. Eine seo_path_info existiert nämlich pro Sales Channel getrennt; dieselbe Adresse kann im einen Shop leben und im anderen tot sein. Erst die Stichprobe mit korrektem Kanal-Filter ergab das saubere Bild.

Praktische Konsequenz für deine eigene Prüfung: Filtere immer auf die sales_channel_id der Domain, die du testest. Sonst misst du Zeilen, die mit der aufgerufenen Domain nichts zu tun haben.

Der Fund, den niemand sieht: kanonische URLs ins Leere

Jetzt zum interessantesten Ergebnis. Wir haben 15 zufällige kanonische, aktive Produkt-URLs abgerufen – also solche, die der Shop selbst für gültig hält.

13 lieferten 200. Zwei lieferten 404.

Die Ursache fand sich per Join gegen die Produkttabelle: Zu diesen Zeilen existiert kein Produkt mehr. Hochgerechnet auf den ganzen Kanal:

38 von 775 kanonischen Produkt-URLs (4,9 %) zeigen auf gelöschte Produkte.

Kein Produkt ist dabei bloß deaktiviert – die Datensätze sind vollständig weg. Die SEO-Zeile blieb stehen, weil sie beim Löschen des Produkts nicht mitentfernt wurde.

Die entscheidende Gegenfrage – und die gute Nachricht

An dieser Stelle wäre die dramatische Schlagzeile leicht gewesen: „5 % der Shop-URLs sind kaputt.” Bevor wir das schreiben, haben wir die einzige Frage gestellt, die für SEO wirklich zählt: Stehen diese URLs in der Sitemap? Denn nur dann würde Google aktiv auf tote Seiten geschickt.

Wir haben die Sitemap heruntergeladen (sitemap.xml → gzip-Datei mit 776 URLs) und alle 38 dagegen geprüft:

0 von 38 stehen in der Sitemap.

Shopware filtert beim Generieren korrekt gegen den Produktbestand. Zur Absicherung noch die Gegenprobe in die andere Richtung: 20 zufällige Sitemap-URLs abgerufen – 20 von 20 mit 200.

Der Befund bleibt also, aber er schrumpft auf sein wahres Maß: Es ist ein Datenbank-Altlastenproblem, kein Indexierungsproblem. Relevant wird es nur dort, wo die URLs von außen erreichbar sind – etwa über alte Backlinks, interne Verlinkungen oder Feeds für Preisportale. Wer Produktfeeds direkt aus seo_url baut statt aus dem Produktbestand, exportiert diese 4,9 % dagegen munter weiter.

Für deinen Shop heißt das: Diese Abfrage zeigt dir die Altlasten in einer Minute.

SELECT s.seo_path_info
FROM seo_url s
LEFT JOIN product p
  ON p.id = s.foreign_key
 AND p.version_id = UNHEX('0fa91ce3e96a4bc2be4bd9ce752c3425')
WHERE s.is_canonical = 1
  AND s.is_deleted = 0
  AND s.route_name = 'frontend.detail.page'
  AND s.sales_channel_id = UNHEX('DEINE_SALES_CHANNEL_ID')
  AND p.id IS NULL;

Der version_id-Filter ist Pflicht – ohne ihn zählst du Versions-Snapshots doppelt.

Zwei gegenübergestellte Wegweiser: links geht ein alter Pfad in einen neuen über, rechts endet der Pfad an einer Wand

Canonical und Paginierung: Was Shopware macht – und was das Plugin macht

Hier wird es für jeden interessant, der Ratgeber liest. Wir haben Kategorieseiten mit Parametern aufgerufen und ausgelesen, was im HTML steht:

Aufruf im Live-Shoprel=canonicalrobots
/reiniger//reiniger/index,follow
/reiniger/?p=2/reiniger/noindex,follow
/reiniger/?order=name-asc/reiniger/index,follow
/reiniger/?properties=abc/reiniger/noindex,follow

Das sieht nach vorbildlichem Shopware-Standard aus. Nur ist es keiner.

Ein Blick in NavigationPageLoader.php im Core zeigt nämlich das Gegenteil:

if ($request->query->has('p') && $request->query->getInt('p') > 1) {
    $canonical .= '?p=' . $request->query->get('p');
}

Shopware hängt die Seitenzahl an den Canonical an – Seite 2 verweist also auf sich selbst, nicht auf Seite 1. Und ein noindex für Paginierung setzt der Core an keiner Stelle; die Treffer für setRobots('noindex,follow') im Core betreffen ausschließlich Warenkorb, Checkout, Konto, Merkzettel und Suche.

Zur Absicherung haben wir dieselben Aufrufe gegen eine plugin-arme Shopware-Demoinstallation gefahren:

Aufruf im Standard-Shopwarerel=canonicalrobots
/CMS/Image//CMS/Image/index,follow
/CMS/Image/?p=2/CMS/Image/?p=2index,follow
/CMS/Image/?order=name-asc/CMS/Image/index,follow
/CMS/Image/?properties=xyz/CMS/Image/index,follow

Damit ist der Unterschied belegt statt vermutet. Im gemessenen Shop stammt das abweichende Verhalten von einem kostenpflichtigen SEO-Plugin (DreiscSeoPro), dessen RobotsTagSubscriber am Ende genau das tut:

$page->getMetaInformation()->setRobots($robotsTag);

Warum das wichtig ist: In sehr vielen Shopware-SEO-Artikeln steht sinngemäß „Shopware setzt bei Filtern und Paginierung automatisch sauber Canonical und noindex”. Für Filterparameter stimmt das (der Canonical zeigt auf die Kategorie ohne Parameter, das ist Core-Verhalten). Für die Paginierung stimmt es nicht – dort macht der Core bewusst das Gegenteil, und das ist sogar die von Google empfohlene Variante, seit rel=next/prev abgeschafft wurde: Jede Seite verweist auf sich selbst und bleibt indexierbar.

Wer also ein SEO-Plugin einsetzt, sollte wissen, dass es diese Entscheidung überschreibt. Ob noindex auf Seite 2 die richtige Wahl ist, hängt vom Shop ab – die falsche Annahme ist, es sei überhaupt Shopwares Entscheidung gewesen.

Zwei identische Schalttafeln nebeneinander, bei der rechten hat ein zusätzliches Modul mehrere Schalter in andere Stellungen gebracht

hreflang: eingeschaltet, konfiguriert – und trotzdem nicht vorhanden

Der gemessene Shop betreibt Sales Channels für Deutschland, Österreich und die Schweiz mit eigenen Domains. Ein Lehrbuchfall für hreflang. Im ausgelieferten HTML einer Produktseite fanden wir:

0 hreflang-Tags.

Zur Kontrolle dieselbe Prüfung am Standard-Shopware: 6 Tags, inklusive x-default. Der Core kann es also.

Die Ursache steckt in HreflangLoader.php, gleich in der ersten Bedingung:

if (!$salesChannelContext->getSalesChannel()->isHreflangActive()) {
    return new HreflangCollection();
}

Und in der Datenbank:

hreflang_active = 0 bei allen 10 Sales Channels.

Das Tückische daran ist nicht der ausgeschaltete Schalter. Es ist, dass die Konfiguration an anderer Stelle hreflang-Einstellungen auf true zeigtproductHrefLang, categoryHrefLang, landingpageHrefLang, alle aktiviert. Nur gehören die zu einem Sitemap-Plugin und steuern hreflang-Angaben in der Sitemap, nicht im HTML. Wer im Admin nach „hreflang” sucht, findet also aktivierte Häkchen und schließt daraus, dass hreflang läuft.

Wir haben auch die Sitemap selbst geprüft: 0 Vorkommen von hreflang oder xhtml:link. Beide Wege sind also stumm.

🔑 Die verallgemeinerbare Lehre: Zwei Einstellungen mit demselben Wort, an zwei verschiedenen Orten, mit zwei verschiedenen Wirkungen. Ein „aktiviert” im Admin ist eine Aussage über eine Einstellung, nicht über das Ergebnis auf der Seite. Der einzige verlässliche Test ist der Blick ins ausgelieferte HTML:

curl -s https://dein-shop.de/ein-produkt/ | grep -o '<link[^>]*hreflang[^>]*>'

Kommt nichts zurück, hast du kein hreflang – unabhängig davon, wie viele Häkchen gesetzt sind.

Vier Länder-Shopfronten nebeneinander, die Verbindungslinien zwischen ihnen sind unterbrochen und ein Schalter steht auf Aus

SEO-URL-Templates: was du wirklich ändern kannst

Die URL-Struktur steuerst du unter Einstellungen → SEO über Twig-Templates. Im gemessenen Shop laufen die Standardwerte:

{# Produkte #}
{{ product.translated.name|lower }}/{{ product.productNumber }}

{# Kategorien #}
{% for part in category.seoBreadcrumb %}{{ part|lower }}/{% endfor %}

Zwei Dinge dazu, die selten erklärt werden.

Erstens: Slugifizierung passiert automatisch. Du musst keine Umlaute ersetzen und keine Leerzeichen behandeln. Shopware umschließt jedes Template mit einem eigenen Escaper (SeoUrlGenerator.php):

$template = '{% autoescape \'' . self::ESCAPE_SLUGIFY . "' %}$template{% endautoescape %}";

Der Escaper führt slugify() plus rawurlencode() aus. „Flauschtücher Bunt” wird zu flauschtuecher-bunt, ohne dass du etwas tust.

Zweitens: |lower wirkt nur dort, wo du es hinschreibst. In unserer Messung tragen URLs aus 2026 weiterhin Großbuchstaben – aber nur im hinteren Teil:

handseife-hygiene-seife-500x-haende-waschen-1000-ml/SW10481

Der Produktname ist klein, die Produktnummer nicht, weil im Template hinter productNumber kein |lower steht. Über die Jahre sieht man das gut:

JahrProdukt-URLs erzeugtdavon mit Großbuchstaben
20214.7752.450
20223.874222
20234.35167
202440620
20252870
202627127

Der Einbruch von 2021 auf 2022 zeigt die Umstellung auf |lower im Namensteil. Der kleine Wiederanstieg 2026 ist kein Rückschritt, sondern schlicht ein Schwung neuer Artikel mit Buchstaben in der Artikelnummer.

Groß- und Kleinschreibung in URLs ist übrigens kein kosmetisches Thema: Webserver behandeln Pfade case-sensitiv, wodurch /Produkt/ und /produkt/ zwei Adressen sind. Wer das vereinheitlichen will, ändert das Template – und erzeugt damit für jedes Produkt eine neue kanonische Zeile, während die alte zur 301-Weiterleitung wird. Bei 2.604 Produkten sind das 2.604 neue Zeilen. Genau so entstehen die 5,36 Adressen pro Produkt.

Praktischer Rat: Template-Änderungen sind billig zu machen und teuer zu wiederholen. Leg die Struktur einmal fest und lass sie dann in Ruhe. Jede weitere Änderung verlängert die Weiterleitungshistorie, und Google muss jede alte Adresse erneut besuchen, um die Weiterleitung zu sehen.

Strukturierte Daten: der Bereich, der überraschend gut aussieht

Hier hatten wir mit Lücken gerechnet und fanden das Gegenteil. Eine Produktseite liefert unter anderem:

Product, Offer, AggregateRating, Review, Brand, BreadcrumbList, Organization, LocalBusiness, OfferShippingDetails, MerchantReturnPolicy, ShippingDeliveryTime, DefinedRegion

Besonders MerchantReturnPolicy und OfferShippingDetails sind erwähnenswert, weil Google beides für Produkt-Rich-Results in der Shopping-Darstellung auswertet und viele Shops es nicht ausliefern.

Was ebenfalls sauber war: rel=canonical mit absoluter URL, ein gefüllter <title>, eine gefüllte <meta name="description"> sowie eine robots.txt mit korrekt gesperrten Bereichen (/checkout, /account, /widgets) und drei Sitemap-Verweisen.

Noch ein eigener Messfehler, offengelegt

Zwischendurch meldete unsere Prüfung „keine Meta-Description” auf allen Produktseiten. Das wäre ein gravierender Befund gewesen. Er war falsch.

Der Grund: Shopware rendert das Tag über mehrere Zeilen, ungefähr so:

<meta name="description"
      content="Microfaser Bodentuch für Sauberkeit bis in die Fugen …">

Unser grep suchte nach <meta name="description" content="…" in einer Zeile und fand nichts. Ein zweites Skript, das mehrzeilig sucht, fand die Beschreibung auf Anhieb.

🔑 Auch das ist übertragbar: Ein Prüfer, der ein bestimmtes Format erwartet, meldet bei abweichender Formatierung nicht „unklar”, sondern „nicht vorhanden”. Beide sehen im Bericht identisch aus. Wer SEO-Checks per grep baut, sollte gegen mindestens eine Seite gegenprüfen, von der er weiß, dass das Element vorhanden ist.

Performance: der Teil, der wirklich weh tut

Core Web Vitals sind bei Shopware selten ein Framework-Problem und fast immer ein Plugin- und Theme-Problem. Der direkte Vergleich zeigt das deutlich:

Live-Shop (Produktseite)Standard-Shopware (Listing)
HTML dekodiert621.290 B193.578 B
HTML übertragen (brotli)83.858 B26.848 B
<script src>5026
Stylesheets71
Inline-Skripte7413
<img>-Elemente428
davon loading="lazy"29

Das HTML ist also rund 3,2-mal so groß wie beim Standard, bei fast doppelt so vielen Skript-Dateien und 74 Inline-Skriptblöcken.

Zwei Einordnungen, damit die Zahlen nicht größer wirken, als sie sind:

Erstens: Kompression rettet viel. 621 KB dekodiert werden zu 84 KB auf der Leitung – Faktor 7,4. Wer „621 KB HTML” ohne diesen Zusatz nennt, erzeugt Panik, wo Sorgfalt angebracht ist. Bei jeder Größenangabe gehört dazu, ob sie übertragen oder dekodiert gemeint ist.

Zweitens: Nicht alles davon ist vermeidbar. Ein produktiver Shop braucht Consent-Verwaltung, Zahlungsintegration und Tracking. Der Vergleich zeigt nicht „der Shop ist schlecht gebaut”, sondern wie viel Gewicht durch Ergänzungen entsteht – und wo der Hebel liegt.

Die drei wirksamsten Hebel aus unserer Praxis, in dieser Reihenfolge:

  1. Skripte zählen, nicht schätzen. 50 externe Skripte sind fast immer Ergebnis mehrfach eingebauter Werkzeuge, nicht einer bewussten Entscheidung. Doppelt eingebundene Tracking-Snippets sind der häufigste Einzelfund.
  2. Bilder konsequent auf loading="lazy" außerhalb des sichtbaren Bereichs. Im gemessenen Shop sind 29 von 42 Bildern lazy – die restlichen 13 lohnen eine Prüfung, ob sie wirklich sofort gebraucht werden.
  3. JavaScript nur laden, wo es gebraucht wird. Slider-Bibliotheken auf Seiten ohne Slider sind der Klassiker.

Wenn du deinen eigenen Shop messen willst, kannst du unseren Core-Web-Vitals-Test nutzen – er läuft ohne Anmeldung und zeigt LCP, CLS und INP für eine beliebige URL.

Eine Waage vergleicht einen leichten, schlanken Seitenstapel mit einem sehr hohen, schweren Stapel

Die häufigsten Shopware-SEO-Fehler – nach Schadenshöhe sortiert

Aus dieser Messung und der laufenden Betreuung des Shops:

1. Template-Änderungen als Routineaufgabe behandeln. Jede Änderung erzeugt eine komplette neue URL-Generation. Die 5,36 Adressen pro Produkt sind das Ergebnis. Einmal festlegen, dann in Ruhe lassen.

2. is_canonical = 0 abfragen. Liefert immer null Treffer, sieht immer beruhigend aus. Richtig ist IS NULL.

3. Den Sales-Channel-Filter vergessen. Eine Adresse kann in einem Kanal leben und im anderen tot sein. Ohne Filter misst du fremde Zeilen.

4. Plugin-Verhalten für Core-Verhalten halten. Besonders bei Canonical und noindex auf Paginierung. Wer das Plugin entfernt, ändert unbemerkt seine Indexierungsstrategie.

5. Auf ein Häkchen im Admin vertrauen statt aufs HTML. Der hreflang-Fall ist das Musterbeispiel: aktivierte Optionen, null Wirkung.

6. Feeds aus seo_url statt aus dem Produktbestand bauen. Dann exportierst du die 4,9 % toten Adressen an Preisportale.

7. Weiterleitungsketten aufbauen. Im gemessenen Shop ist jede Weiterleitung ein einziger Sprung – das ist der Zielzustand und keineswegs selbstverständlich, wenn zusätzlich Server-Regeln im Spiel sind.

8. noindex und Canonical gleichzeitig auf dieselbe Seite setzen. Widersprüchliche Signale: Ein Canonical sagt „werte jene Seite”, ein noindex sagt „vergiss diese hier”. Google entscheidet dann selbst.

Deine Prüfliste: acht Befehle, ein Bild

Alles, was wir hier gemessen haben, kannst du in wenigen Minuten selbst prüfen. Kein Werkzeug nötig außer curl.

SHOP="https://dein-shop.de"
URL="$SHOP/ein-produkt/"

# 1. Canonical vorhanden und absolut?
curl -s "$URL" | tr -d '\n' | grep -o '<link[^>]*rel="canonical"[^>]*>'

# 2. hreflang wirklich im HTML? (leer = nicht vorhanden)
curl -s "$URL" | tr -d '\n' | grep -o '<link[^>]*hreflang[^>]*>'

# 3. robots-Angabe auf Paginierung
curl -s "$SHOP/eine-kategorie/?p=2" | tr -d '\n' \
  | grep -o '<meta[^>]*name="robots"[^>]*>'

# 4. Leitet eine alte URL weiter – und mit wie vielen Sprüngen?
curl -s -o /dev/null -w '%{http_code} -> %{redirect_url}\n' "$SHOP/alte-url/"

# 5. Sitemap erreichbar und gefüllt?
curl -s "$SHOP/sitemap.xml" | grep -c "<loc>"

# 6. robots.txt korrekt?
curl -s "$SHOP/robots.txt"

# 7. Übertragene vs. dekodierte Größe
curl -s -o /dev/null -w 'transfer: %{size_download} B\n' \
  -H 'Accept-Encoding: br,gzip' "$URL"
curl -s --compressed "$URL" | wc -c

# 8. Wie viele Skripte lädt die Seite?
curl -s --compressed "$URL" | grep -o '<script[^>]*src="' | wc -l

Wichtig ist das tr -d '\n' in den ersten drei Befehlen – genau daran ist unsere eigene Prüfung zunächst gescheitert.

Für die Titel-Optimierung, die dabei fast immer als Nächstes ansteht, haben wir außerdem einen Meta-Title-Generator gebaut.

Fazit: Shopwares SEO-Fundament ist besser als sein Ruf

Nach dieser Messung fällt unser Urteil differenzierter aus, als wir erwartet hatten.

Was Shopware von Haus aus richtig macht: Die Weiterleitungslogik funktioniert zuverlässig (25 von 25 mit sauberem Einzelsprung). Die Sitemap ist diszipliniert und enthält keine toten Produkte (0 von 38). Strukturierte Daten sind umfangreicher als bei vielen Konkurrenzsystemen. Der Canonical-Umgang mit Filterparametern ist korrekt, und die Paginierungslogik des Cores entspricht sogar der aktuellen Google-Empfehlung.

Wo es wirklich klemmt: Nicht im Framework, sondern in dem, was darum herum wächst. Ein ausgeschalteter hreflang-Schalter, den zwei gleichnamige Plugin-Optionen verdecken. Ein SEO-Plugin, das die Indexierungsstrategie des Cores still überschreibt. 4,9 % Altlasten in der URL-Tabelle. Und ein HTML-Dokument, das durch Ergänzungen auf das 3,2-Fache angewachsen ist.

Die drei Sätze, die wir aus dieser Messung mitnehmen:

  1. Ein Häkchen im Admin ist eine Absicht, kein Ergebnis. Nur das ausgelieferte HTML zählt.
  2. Eine leere Abfrage sieht aus wie eine beruhigende Antwort. is_canonical = 0 liefert für immer null Treffer.
  3. Prüfe, ob Verhalten aus dem Core oder aus einem Plugin kommt – sonst optimierst du gegen eine Annahme.

Wenn du tiefer einsteigen willst: In unserem Deep-Dive zur Shopware Installation messen wir denselben Shop von der anderen Seite, und Shopware Cloud ordnet ein, was du in der gehosteten Variante überhaupt beeinflussen kannst. Wer noch bei der Systemwahl steht, findet in Shopify vs. Shopware und Was kostet Shopware 6 die kommerzielle Seite. Und für die Suchmaschinen jenseits von Google lohnt der Blick in Bing SEO sowie Google KI-SEO; welches Analysewerkzeug dazu passt, klärt Sistrix vs. Ahrefs.

Häufige Fragen zu Shopware SEO

Ist Shopware gut für SEO?

Ja, das Fundament ist solide. In unserer Messung an einem Live-Shop funktionierten Weiterleitungen zuverlässig (25 von 25 alte URLs mit sauberem 301 in genau einem Sprung), die Sitemap enthielt keine toten Produkte (0 von 38 problematischen URLs), und die strukturierten Daten umfassten Product, Offer, AggregateRating, MerchantReturnPolicy und OfferShippingDetails. Die typischen Probleme entstehen nicht im Kern, sondern durch Plugins, Themes und über Jahre gewachsene Datenaltlasten.

Was sind SEO-URLs in Shopware und wo stelle ich sie ein?

SEO-URLs sind die sprechenden Adressen deines Shops, gespeichert in der Tabelle seo_url. Du steuerst ihre Struktur unter Einstellungen → SEO über Twig-Templates, standardmäßig {{ product.translated.name|lower }}/{{ product.productNumber }} für Produkte und {% for part in category.seoBreadcrumb %}{{ part|lower }}/{% endfor %} für Kategorien. Umlaute und Leerzeichen musst du nicht behandeln – Shopware umschließt jedes Template automatisch mit einem Slugify-Escaper.

Leitet Shopware alte URLs automatisch weiter?

Ja, sofern core.seo.redirectToCanonicalUrl aktiv ist. Entscheidend ist die Spalte is_deleted: Bei is_deleted = 0 antwortet die alte Adresse mit 301 und leitet auf die aktuelle URL weiter, bei is_deleted = 1 mit 404. Wir haben beides getestet – 25 von 25 nicht-gelöschten URLs lieferten 301 in einem einzigen Sprung, 12 von 12 gelöschten lieferten 404.

Warum finde ich mit is_canonical = 0 keine Weiterleitungs-URLs?

Weil Shopware in diese Spalte niemals 0 schreibt. Im Quelltext (SeoUrlPersister.php) steht $insert['is_canonical'] = (...) ? 1 : null; – der Wert ist also entweder 1 oder NULL. Da NULL = 0 in SQL nicht „falsch”, sondern „unbekannt” bedeutet, liefert die Abfrage immer null Treffer, ohne einen Fehler zu melden. Richtig ist WHERE is_canonical IS NULL. In unserem Shop machte das den Unterschied zwischen „0” und den tatsächlichen 6.350 Zeilen aus.

Setzt Shopware bei Paginierung automatisch noindex?

Nein. Der Shopware-Core setzt für Kategorieseiten mit ?p=2 weder noindex noch einen Canonical auf Seite 1 – er hängt die Seitenzahl im Gegenteil an den Canonical an ($canonical .= '?p=' . ... in NavigationPageLoader.php), sodass jede Seite auf sich selbst verweist und indexierbar bleibt. Wir haben das an einer plugin-armen Standardinstallation bestätigt: ?p=2 liefert dort canonical=/CMS/Image/?p=2 und robots=index,follow. Ein noindex auf Paginierung stammt praktisch immer von einem SEO-Plugin.

Wie prüfe ich, ob hreflang in meinem Shopware-Shop wirklich funktioniert?

Nur über das ausgelieferte HTML, nicht über den Admin. Ruf die Seite mit curl ab, entferne die Zeilenumbrüche mit tr und durchsuche das Ergebnis nach Alternate-Link-Tags mit hreflang-Attribut — den exakten Befehl findest du oben in der Prüfliste. Das zeigt dir die tatsächlich vorhandenen Tags. In unserem Shop kamen 0 Tags zurück, obwohl im Admin mehrere hreflang-Optionen aktiviert waren – diese gehörten zu einem Sitemap-Plugin. Der entscheidende Schalter ist hreflang_active am Sales Channel, der bei allen zehn Kanälen auf 0 stand; HreflangLoader.php steigt in diesem Fall sofort mit einer leeren Sammlung aus.

Wie finde ich SEO-URLs, die auf gelöschte Produkte zeigen?

Mit einem LEFT JOIN von seo_url auf product und der Bedingung p.id IS NULL, gefiltert auf is_canonical = 1, is_deleted = 0 und deine sales_channel_id. Der version_id-Filter auf der Produkttabelle ist Pflicht, sonst zählst du Versions-Snapshots doppelt. In unserem Shop fanden wir so 38 von 775 kanonischen Produkt-URLs (4,9 %), die ins Leere zeigten. Wichtig zur Einordnung: Keine davon stand in der Sitemap – es ist ein Datenbank-, kein Indexierungsproblem.

Warum haben meine Shopware-URLs Großbuchstaben?

Meist aus zwei Gründen. Entweder stammen sie aus einer Zeit vor dem |lower-Filter im Template – in unserem Shop hatten 2.450 von 4.775 URLs aus 2021 Großbuchstaben, 2025 waren es null. Oder der Filter fehlt an einer Stelle: Steht |lower nur hinter dem Produktnamen, bleibt die Produktnummer groß (.../SW10481). Da Webserver Pfade case-sensitiv behandeln, sind /Produkt/ und /produkt/ zwei verschiedene Adressen.

Wie viele alte URLs pro Produkt sind normal?

Das hängt davon ab, wie oft du Templates und Produktnamen geändert hast. In unserem seit 2021 gewachsenen Shop kommen auf 2.604 Produkte 13.964 URL-Zeilen – im Schnitt 5,36 Adressen pro Produkt, im Extremfall 32. Das ist kein Fehler, sondern die gewollte Weiterleitungshistorie. Problematisch wird es erst, wenn Änderungen zur Routine werden, denn jede erzeugt eine komplette neue URL-Generation.

Macht Shopware meinen Shop langsam?

Der Kern nicht. In unserem Vergleich lieferte eine Standard-Shopware-Seite 193.578 Byte HTML mit 26 Skript-Dateien, die produktive Shopseite dagegen 621.290 Byte mit 50 Skript-Dateien, 7 Stylesheets und 74 Inline-Skriptblöcken – also rund das 3,2-Fache. Der Zuwachs kommt aus Theme, Tracking, Consent und Plugins, nicht aus dem Framework. Zur Einordnung gehört außerdem, dass komprimiert nur 83.858 Byte übertragen werden.

Brauche ich ein SEO-Plugin für Shopware?

Nicht zwingend – aber es ändert mehr, als vielen bewusst ist. Canonical-Behandlung von Filterparametern, Weiterleitungen, Sitemap und strukturierte Daten liefert Shopware bereits selbst. Ein Plugin bringt vor allem Komfort und zusätzliche Steuerung, etwa noindex auf Paginierung oder frei setzbare Canonicals. Wichtig ist zu wissen, dass es damit die Standardentscheidungen des Cores überschreibt: Wer das Plugin später entfernt, ändert unbemerkt seine Indexierungsstrategie.