Zum Inhalt springen

Korrektes hreflang in Next.js — generiert, nicht gepflegt

Die meisten mehrsprachigen Next.js-Sites liefern einen kaputten hreflang-Cluster aus. Der Fehler ist immer derselbe — die Tags werden von Hand geschrieben.

4 Min. Lesezeit

Die hreflang-Spezifikation ist kurz und die Regeln sind einfach. Fast jede Implementierung, die wir auditieren, macht es trotzdem falsch, und fast immer aus derselben Grundursache: jemand pflegt die Alternates von Hand, die Übersetzungen bewegen sich, und die Tags nicht.

Die drei Regeln, die gebrochen werden

1. Jede Seite eines Clusters muss jede Seite des Clusters auflisten, sich selbst eingeschlossen. hreflang ist bidirektional. Zeigt Ihre englische Seite auf die deutsche, die deutsche aber nicht zurück, verwirft Google die Beziehung. Die Selbstreferenz ist nicht optional.

2. Werben Sie nicht mit einer Übersetzung, die es nicht gibt. hreflang="de" auf eine englische Seite zeigen zu lassen, weil die deutsche Fassung „bald kommt", ist schlimmer als sie wegzulassen. Sie sagen dem Crawler, dass Deutschsprachige eine deutsche Seite haben — und enttäuschen sie dann.

3. x-default ist für den Fallback, nicht für Englisch. Es markiert die Seite, die Nutzern ausgeliefert wird, deren Sprache Sie nicht adressieren — meist Ihre Sprachauswahl oder Ihre Standardsprache, falls Sie keine haben.

Warum handgepflegte Tags immer verrotten

Betrachten Sie den Lebenszyklus. Sie starten auf Englisch und Deutsch. Jemand schreibt die Metadaten:

// The version that will be wrong within a month
alternates: {
  languages: {
    en: 'https://example.com/pricing',
    de: 'https://example.com/de/pricing',
  },
}

Dann kommt Spanisch dazu, aber nur für die Marketing-Seiten. Dann wird ein deutscher Artikel depubliziert. Dann benennt jemand einen Slug um. Jedes davon ist ein eigener Pull Request, in einem anderen Teil der Codebasis, und keiner davon fasst dieses Objekt an.

Sechs Monate später meldet die Search Console „keine Rückverweis-Tags" für ein paar hundert URLs, und niemand kann rekonstruieren, warum.

Aus dem Inhalt generieren, nicht aus einer Liste

Die Lösung besteht darin, die Alternates zu einem abgeleiteten Wert zu machen. In der Codebasis dieser Site — die öffentlich ist — beantwortet die Content-Schicht eine Frage:

/**
 * Which locales actually have this document. Translations are published as
 * they are written, so a page's hreflang cluster must reflect reality.
 */
export function availableLocalesFor(
  collection: Collection,
  slug: string,
): Locale[] {
  return locales.filter((locale) =>
    fs.existsSync(path.join(collectionDir(collection, locale), `${slug}.mdx`)),
  );
}

Und der Metadaten-Helfer konsumiert sie:

const languages: Record<string, string> = {};
for (const l of availableLocales) {
  languages[hreflangMap[l]] = localizedUrl(l, path);
}
 
// `x-default` only makes sense when the default locale really has the page.
if (availableLocales.includes(defaultLocale)) {
  languages['x-default'] = localizedUrl(defaultLocale, path);
}
 
return {
  alternates: { canonical: localizedUrl(locale, path), languages },
  // ...
};

Löschen Sie eine Übersetzungsdatei, und das Tag verschwindet beim nächsten Build. Fügen Sie eine hinzu, und es erscheint. Es gibt keine Liste, die man zu aktualisieren vergessen kann, weil es keine Liste gibt.

Die Selbstreferenz kommt gratis: die aktuelle Sprache ist per Definition in availableLocales und wird deshalb immer ausgegeben.

Sprachcodes, und wann eine Region dazugehört

Verwenden Sie den bloßen Sprachcode, sofern Sie nicht wirklich unterschiedliche Inhalte an unterschiedliche Regionen ausliefern:

  • de — Deutschsprachige überall.
  • de-AT — nur wenn österreichische Besucher andere Preise, Verfügbarkeiten oder Rechtstexte bekommen als deutsche.

Ein Regionscode, den Sie nicht begründen können, teilt Ihre Signale ohne Nutzen auf zwei Cluster auf. Die üblichen Ausnahmen sind echt: en-GB gegen en-US für Schreibweise und Währung, es-MX gegen es-ES für Vokabular und Preise, pt-BR gegen pt-PT.

Die Region ist für sich genommen nie ein Länder-Targeting. hreflang="de" heißt nicht „in Deutschland zeigen"; es heißt „diese Seite ist für Deutschsprachige".

Arabisch und andere RTL-Sprachen

Rechts-nach-links-Sprachen brauchen von hreflang nichts Besonderes — ar ist ein Sprachcode wie jeder andere. Was sie brauchen, ist ein korrektes dir am Dokument, und das muss pro Sprache gesetzt werden:

export default async function LocaleLayout({ children, params }) {
  const { locale } = await params;
 
  return (
    <html lang={locale} dir={dirFor(locale)}>
      {/* ... */}
    </html>
  );
}

Schreiben Sie Ihr Layout dann in logischen CSS-Eigenschaften — padding-inline-start statt padding-left, margin-inline-end statt margin-right, text-align: start statt left. Tailwinds ps-*, pe-*, ms-* und me-* bilden genau das ab. Machen Sie das von Anfang an, und das arabische Layout spiegelt sich von selbst; rüsten Sie es später nach, und Sie verbringen eine Woche damit, die zwölf Stellen zu finden, die es nicht taten.

Die Ausnahmen, die nicht spiegeln sollten: Codeblöcke, E-Mail-Adressen, URLs und Markennamen in lateinischer Schrift. Setzen Sie dort explizit direction: ltr.

Wie Sie es überprüfen

Drei Prüfungen, in aufsteigender Verlässlichkeit:

  1. Den Quelltext ansehen. Nicht den Inspektor — der zeigt das hydratisierte DOM. curl -s https://example.com/de/pricing | grep alternate zeigt, was der Crawler bekommt.
  2. Search Console → Internationale Ausrichtung. Meldet „keine Rückverweis-Tags" und „unbekannter Sprachcode" für die gesamte Property.
  3. Crawlen. Screaming Frog oder Sitebulb kartieren den vollständigen Cluster und sagen Ihnen, welchen Seiten reziproke Tags fehlen. Das fängt die Fälle ab, in denen die Tags einzeln gültig sind, der Graph aber unvollständig ist.

Die Sitemap muss zustimmen

hreflang kann im HTML-Head oder in der Sitemap stehen. Wenn Sie beides ausgeben — und das sollten Sie — müssen sie exakt übereinstimmen, denn ein Widerspruch zwischen ihnen wird unvorhersehbar aufgelöst.

Dasselbe Prinzip gilt: generieren Sie die Sitemap aus demselben Helfer, den auch die Seiten verwenden. Das behandeln wir, zusammen mit der Korrektheit kanonischer URLs, in Canonicals und Sitemaps, die nicht auseinanderdriften können.

Zurück zu allen Artikeln