Sylius SEO Suite Pro: hreflang
hreflang in Sylius SEO Suite Pro: Welche Seiten zusammengehören, Channel-Gruppen, Regeln für gelistete Sprachen, x-default, Einstellungen im Admin und Ausgabe in Storefront und Shop API.
Sylius SEO Suite Pro: hreflang
Das Plugin gibt <link rel="alternate" hreflang="..." href="..."> im <head> von Produktseiten, Kategorie-Listings und der Startseite aus. Dieselbe Liste steht in der Shop API im Feld alternates. Eine Seite mit weniger als zwei Alternativen bekommt nichts. Ein Shop mit einer Sprache pro Channel liefert daher genau das aus, was er vorher ausgeliefert hat.
<link rel="alternate" hreflang="de-DE" href="https://shop.example.de/de_DE/products/blaues-sofa">
<link rel="alternate" hreflang="en-US" href="https://shop.example.com/en_US/products/blue-sofa">
<link rel="alternate" hreflang="x-default" href="https://shop.example.de/de_DE/products/blaues-sofa">
Welche Seiten zusammengehören
Channels sind oft eigenständige Shops mit eigenem Sortiment und keine Übersetzungen voneinander. Das Plugin rät deshalb nie über Channels hinweg:
- Ohne Gruppe sind die Alternativen die Sprachen des eigenen Channels: dieselbe Produkt-, Kategorie- oder Startseite in jeder Sprache des Channels, auf dem Host des Channels.
- Mit einer Gruppe (SEO Suite Pro > hreflang, eine Zeile pro Channel, derselbe
group_codein jeder Zeile) wird derselbe Produkt- oder Kategorie-Code über alle aktiven Channels der Gruppe verlinkt, jeder Channel auf seinem eigenen Hostnamen, mit dem Schema der aktuellen Anfrage. Ein Produkt oder eine Kategorie nimmt nur an einem Channel teil, in dem es verfügbar ist. Die Sprachen jedes Channels zählen.
Gruppiert wird nach dem Code des Produkts oder der Kategorie. Für die Startseite zählt die Startseite jedes Gruppen-Channels.
Das Set wird immer aus der Gruppe gebaut, nie aus der Seite, die gerade gerendert wird. Jede Seite eines Sets gibt dieselbe vollständige Liste aus, sich selbst eingeschlossen.
Beispiel: zwei Channels in einer Gruppe
Der Channel shop_de (Hostname shop.example.de, Sprache de_DE) und der Channel shop_en (Hostname shop.example.com, Sprache en_US) haben denselben group_code:
| Channel | Aktiv | hreflang group | x-default language |
|---|---|---|---|
shop_de |
ja | main |
leer |
shop_en |
ja | main |
leer |
Das Produkt wird in beiden Channels verkauft und hat die Slugs blaues-sofa (de_DE) und blue-sofa (en_US). Auf beiden Produktseiten steht dann derselbe Block:
<link rel="alternate" hreflang="de-DE" href="https://shop.example.de/de_DE/products/blaues-sofa">
<link rel="alternate" hreflang="en-US" href="https://shop.example.com/en_US/products/blue-sofa">
<link rel="alternate" hreflang="x-default" href="https://shop.example.de/de_DE/products/blaues-sofa">
Der x-default zeigt auf die Standardsprache des ersten Channels der Gruppe (niedrigste Zeilen-ID), hier shop_de.
Entscheidungsablauf
graph TD
A["Seite wird gerendert"] --> B{"Channel-Einstellung aktiv?"}
B -->|"Nein"| Z["Keine hreflang-Links"]
B -->|"Ja"| C{"Seite ist kanonisch: kein noindex, keine Facette, nicht Seite 2 oder höher, kein abweichender Canonical?"}
C -->|"Nein"| Z
C -->|"Ja"| D["Set aufbauen: Sprachen des Channels oder aller aktiven Gruppen-Channels"]
D --> E["Pro hreflang-Wert eine Seite: Duplikate fallen weg"]
E --> F{"Ist die Seite selbst im Set?"}
F -->|"Nein"| Z
F -->|"Ja"| G{"Genug Alternativen und genug verschiedene URLs?"}
G -->|"Nein"| Z
G -->|"Ja"| H["x-default bestimmen und anhängen"]
H --> I["Alternates im head und in der Shop API ausgeben"]
Regeln: wann eine Sprache gelistet wird
- Wert: die Sylius-Locale mit Bindestrich.
de_DEwird zude-DE, die Regel des Sitemap-Plugins. Suchmaschinen akzeptieren nur eine Region nach ISO 3166-1 alpha-2, deshalb entfällt eine numerische Region:es_419wird zues. Mithreflang.format: languagebleibt nur die Sprache (de). - Eine Seite pro Wert: Würden zwei Channels einer Gruppe denselben Wert beanspruchen (zwei Channels mit
de_DE, oderde_DEundde_ATim Formatlanguage), ist das ein Konfigurationsfehler. Der Channel, dessen Zeile die niedrigere ID hat, behält den Wert, der andere fällt aus dem Set, und es wird eine Warnung geloggt. Die Seite des herausgefallenen Channels bekommt dann gar kein hreflang, weil sie nicht Teil ihres eigenen Sets wäre. - Seiten, die existieren: Ein deaktiviertes Produkt oder eine deaktivierte Kategorie, ein Produkt, das im Channel nicht verkauft wird, eine Kategorie außerhalb der Menü-Kategorie des Channels (oder unterhalb einer deaktivierten) sowie eine Sprache ohne Übersetzung oder ohne Slug werden ausgelassen. Die Wurzel eines Kategoriebaums ist ein Gruppierungsknoten ohne eigene Seite.
- Seiten, die indexierbar sind: Eine Übersetzung mit
noindexoder mit einem Canonical, der woandershin zeigt, bleibt aus jedem Set heraus (dieselbe Regel wie beiSitemapExclusionInterface), und eine solche Seite bekommt selbst keine Alternativen. Dasselbe gilt für gefilterte oder sortierte Listings (jede Anfrage mit einem Facetten-Parameter) und für Seite 2 und weitere eines Listings. - Hostname: Jeder Channel einer Gruppe braucht einen Hostnamen, sonst kann kein anderer Channel auf ihn verlinken. Er fällt dann aus deren Alternativen heraus, und es wird einmalig eine Warnung geloggt.
- Minimum:
hreflang.min_alternates(Standard 2). Der x-default zählt nicht mit. Es müssen außerdem mindestens so viele verschiedene URLs vorhanden sein: Ein Shop, der alle Sprachen unter einer URL ausliefert (kein Locale-Präfix), bekommt nichts.
Eine Seite muss Teil ihres eigenen Sets sein (Selbstreferenz). Fehlt ihr eigener Eintrag, etwa weil sie noindex ist oder ihr Wert vergeben war, werden gar keine Alternativen ausgegeben, auch nicht für die übrigen Sprachen.
x-default
Es gibt genau einen x-default pro Set. Er wird so bestimmt:
- Der Reihe nach wird jeder Channel der Gruppe gefragt, beginnend mit der niedrigsten Zeilen-ID.
- Hat der Channel eine x-default-Sprache eingestellt, zählt die Seite in dieser Sprache. Sie darf auch zu einem anderen Channel der Gruppe gehören (das Formular akzeptiert jede), die Seite des eigenen Channels hat Vorrang.
- Ist keine Sprache eingestellt, zählt die Standardsprache des Channels.
- Gibt es diese Seite nicht, wird der nächste Channel gefragt.
- Gibt es für keinen Channel eine passende Seite, wird die erste Alternative als x-default genommen.
Entscheidung bei Duplikat-Werten
| Situation | Format | Ergebnis |
|---|---|---|
Zwei Channels einer Gruppe haben de_DE |
beliebig | Der Channel mit der niedrigeren Zeilen-ID behält de-DE, der andere fällt heraus. Speichern im Admin wird verweigert. |
Ein Channel hat de_DE, ein anderer de_AT |
language-region |
Zwei Werte de-DE und de-AT, alles in Ordnung |
Ein Channel hat de_DE, ein anderer de_AT |
language |
Beide ergeben de: Konflikt wie oben |
Einstellungen
Zwei Schlüssel unter mmd_sylius_seo_suite_pro.hreflang:
| Einstellung | Standard | Beschreibung |
|---|---|---|
hreflang.format |
language-region |
Schreibweise des Werts: language-region macht aus de_DE den Wert de-DE, language behält nur die Sprache (de) |
hreflang.min_alternates |
2 |
Eine Seite bekommt hreflang-Links nur, wenn ihr Set mindestens so viele Alternativen hat (x-default nicht mitgezählt). Mindestens 1. |
# config/packages/mmd_sylius_seo_suite_pro.yaml
mmd_sylius_seo_suite_pro:
hreflang:
format: language-region
min_alternates: 2
Mehr dazu unter Konfiguration.
Einstellungen im Admin
Öffne SEO Suite Pro > Schaltfläche hreflang. Dort gibt es eine optionale Zeile pro Channel, ein Channel ohne Zeile verwendet die Standardwerte (hreflang an, keine Gruppe, Standardsprache des Channels als x-default). Es gibt keinen eigenen Seitenleisten-Eintrag, über Zurück zu SEO Suite Pro kommst Du zurück.
| Feld | Standard | Beschreibung |
|---|---|---|
| Channel | Eine Zeile pro Channel, jeder Channel nur einmal | |
| Output hreflang links | an | Aus: Der Channel gibt keine Links aus und wird auch von den anderen Channels seiner Gruppe nicht verlinkt |
| hreflang group | leer | Optionaler Code aus Kleinbuchstaben, Ziffern, - und _, beginnend mit Buchstabe oder Ziffer, höchstens 64 Zeichen. Gleicher Code bedeutet, dass die Channels Alternativen voneinander sind. |
| x-default language | leer | Sprache für Besucher ohne passende Sprache. Muss zum Channel (oder in einer Gruppe zu einem Channel der Gruppe) gehören. Leer bedeutet die Standardsprache des Channels. In einer Gruppe zählt die Einstellung des ersten Channels (niedrigste Zeile). |
Beim Speichern prüft ValidHreflangSettings:
- Die x-default-Sprache muss von diesem Channel oder, in einer Gruppe, von einem Channel der Gruppe angeboten werden.
- Jeder aktive Channel einer Gruppe braucht einen Hostnamen.
- Kein hreflang-Wert darf zweimal vorkommen (mit dem eingestellten
hreflang.formatgerechnet). Gilt auch ohne Gruppe für die Sprachen eines Channels, zum Beispielde_DEundde_ATim Formatlanguage.
Die Priorität in einer Gruppe ist die ID der Zeile. Die niedrigste ID gewinnt bei Duplikaten und bestimmt den x-default. Eine neu angelegte Zeile hat immer die höchste ID und steht damit am Ende.
Eigene Seitentypen
Wer eigene Seitentypen (zum Beispiel Landingpages) mit hreflang versehen will, implementiert Mmd\SyliusSeoSuiteProPlugin\Hreflang\HreflangAlternatesProviderInterface. Der Provider sagt nur, in welchen Sprachen eines Channels eine Seite existiert. Gruppierung, Duplikate, x-default und Host übernimmt das Plugin. Die Details stehen unter Erweitern. Eingebaut sind Produkt, Kategorie und Startseite.
Storefront und Shop API
Im Storefront rendert das Template die Links für jeden Eintrag von seo.alternates. Das gilt für Produktseiten, Kategorie-Listings und die Startseite.
Die Shop API liefert dieselbe Liste im Feld alternates der Seo-Ressource, gebaut vom selben Service:
GET /shop/seo/products/{code}GET /shop/seo/taxons/{code}
Beide Pfade liegen unter dem API-Präfix Deines Shops (bei Sylius standardmäßig /api/v2). Das Feld ist eine Liste aus {hreflang, href}, leer, wenn die Seite keine Alternativen bekommt:
{
"alternates": [
{ "hreflang": "de-DE", "href": "https://shop.example.de/de_DE/products/blaues-sofa" },
{ "hreflang": "en-US", "href": "https://shop.example.com/en_US/products/blue-sofa" },
{ "hreflang": "x-default", "href": "https://shop.example.de/de_DE/products/blaues-sofa" }
]
}
Die URLs verwenden die Hostnamen der Channels und das Schema der API-Anfrage. Ein Headless-Frontend auf einem anderen Host ordnet sie selbst zu. Für die Startseite gibt es keinen API-Endpunkt, ihre Alternativen rendert nur Twig. Mehr zur API steht unter Shop API.
Kosten
Die Einstellungen aller Channels werden mit einer Abfrage pro Request gelesen, ein weiteres Query holt die Übersetzungen der Seite, und alle Channels mit Locales und Menü-Kategorie kommen in einer einzigen Abfrage. Weder die Zahl der Sprachen noch die Zahl der Channels einer Gruppe erzeugt zusätzliche Abfragen. Fehlt die Tabelle (Migration noch nicht gelaufen), gelten die Standardwerte, und es wird einmalig eine Warnung geloggt. Schlägt der Aufbau der Alternativen fehl, wird die Seite ohne Alternativen gerendert, nie mit einem Fehler.
Weitere Hilfe: Fehlerbehebung.