Blog/Web-Engineering

Mehrsprachiges SEO mit Nuxt i18n: URL-Strategie, hreflang, Canonicals und Sitemaps

So richtet man Nuxt i18n ein, damit Suchmaschinen jede Sprache indexieren: Routing-Strategie, useLocaleHead, hreflang-Regeln, Teilübersetzungen, Sitemaps.

··8 Min. Lesezeit

  • Nuxt
  • i18n
  • SEO
  • hreflang
  • Multilingual
Drei Sprachversionen einer Seite, Englisch, Deutsch und Ungarisch, mit hreflang-Pfeilen untereinander verknüpft und einem x-default-Fallback.

Das Wichtigste in Kürze

  • Jeder Sprache eine eigene URL geben; Google empfiehlt das statt Cookies oder Browsereinstellungen, und der Googlebot sendet keinen Accept-Language-Header.
  • In @nuxtjs/i18n bei jeder Locale language und eine produktive baseUrl setzen, dann liefert useLocaleHead lang-, hreflang-, og:locale- und Canonical-Tags.
  • hreflang-Sätze müssen auf jeder Version identisch sein, die Seite selbst enthalten, vollständige URLs und gültige ISO-Sprachcodes nutzen.
  • Bei Seiten in weniger Sprachen nur die echten Übersetzungen listen und den Canonical unübersetzter Kopien aufs Original zeigen lassen.
  • Nicht nach Browsersprache umleiten; einen Sprachumschalter mit echten Links anbieten und hreflang die Suchenden zur richtigen Version schicken lassen.

Diesen Artikel anhören

0:000:00

Eine mehrsprachige Nuxt-Seite kann für Besucher perfekt aussehen und für Suchmaschinen trotzdem halb unsichtbar sein: Die deutschen Seiten werden nicht indexiert, Google zeigt ungarischen Suchenden die englische Seite, oder zwei Sprachversionen konkurrieren um dieselbe Suchanfrage. Fast immer liegt die Ursache im Head der Seite: fehlende oder widersprüchliche hreflang-Links, ein Canonical auf die falsche Sprache oder eine URL-Strategie, die Sprachen hinter einem Cookie versteckt.

Dieser Artikel zeigt ein Setup mit @nuxtjs/i18n, das Suchmaschinen verstehen, am Beispiel dieser Website, die statisch auf Englisch, Deutsch und Ungarisch generiert wird. Es geht um die Routing-Strategie, useLocaleHead, die hreflang-Regeln aus Googles Dokumentation, Seiten, die nur in manchen Sprachen existieren, Sitemaps und warum automatische Sprachweiterleitungen schaden.

Welche URL-Strategie sollte eine mehrsprachige Nuxt-Seite nutzen?

Googles Empfehlung für mehrsprachige Websites ist eindeutig: für jede Sprachversion eigene URLs statt Cookies oder Browsereinstellungen. Wechselt der Inhalt auf einer URL je nach Sprache, findet Google womöglich nicht alle Varianten, denn der Googlebot crawlt meist aus den USA und sendet Anfragen ohne Accept-Language-Header. Nuxt i18n bietet vier Routing-Strategien:

StrategieURLsSEO-Urteil
prefix_except_default/blog, /de/blog, /hu/blogGuter Standard; die Hauptsprache behält saubere URLs
prefix/en/blog, /de/blog, /hu/blogGut; symmetrisch, braucht eine Weiterleitung oder Sprachauswahl an der Wurzel
prefix_and_default/blog and /en/blog, /de/blogFunktioniert, erzeugt aber Duplikate; der Canonical zeigt auf die URL ohne Präfix
no_prefix/blog for allFür öffentliche Inhalte meiden; eine URL wird nur in einer Sprache indexiert

Unterverzeichnisse auf einer Domain sind auch im Betrieb die günstigste Option. Eigene Länderdomains lohnen sich, wenn das Geschäft pro Land wirklich getrennt ist; für eine zweite Sprache braucht man sie nicht.

Wie konfiguriert man @nuxtjs/i18n fürs SEO?

Zwei Einstellungen sind nötig, damit die SEO-Funktionen korrekte Ausgaben liefern: Jede Locale braucht ein language-Tag, das zum hreflang-Wert und zum <html lang> wird, und baseUrl muss der produktive Origin sein, denn hreflang- und Canonical-URLs müssen vollständig sein. So sieht die Konfiguration dieser Website aus:

// nuxt.config.ts
export default defineNuxtConfig({
  modules: ['@nuxtjs/i18n'],
  i18n: {
    baseUrl: 'https://balazscsorba.com',
    strategy: 'prefix_except_default',
    defaultLocale: 'en',
    locales: [
      { code: 'en', language: 'en-US', name: 'English', file: 'en.json' },
      { code: 'de', language: 'de-AT', name: 'Deutsch', file: 'de.json' },
      { code: 'hu', language: 'hu-HU', name: 'Magyar', file: 'hu.json' },
    ],
    // No automatic redirect by browser language; the switcher links to every version.
    detectBrowserLanguage: false,
  },
})

Was schreibt useLocaleHead in den Head der Seite?

useLocaleHead() liefert Head-Metadaten, die man an useHead übergibt: das lang-Attribut für <html>, einen Alternate-Link mit hreflang für jede konfigurierte Locale, og:locale- und og:locale:alternate-Meta-Tags und einen Canonical-Link. Einmal in app.vue aufrufen, dann bekommt es jede Seite.

<!-- app.vue -->
<script setup lang="ts">
const i18nHead = useLocaleHead({ dir: true, seo: true })

useHead(() => ({
  htmlAttrs: { lang: i18nHead.value.htmlAttrs?.lang },
  link: [...(i18nHead.value.link ?? [])],
  meta: [...(i18nHead.value.meta ?? [])],
}))
</script>

Für eine Seite unter /de/blog/post enthält der gerenderte Head dann <html lang="de-AT">, einen Canonical-Link auf die deutsche URL und Alternate-Links für en-US, de-AT und hu-HU. Das Modul ergänzt außerdem einen Catch-all-Link pro Sprachgruppe, etwa hreflang="en", und ein x-default. Das generierte HTML einmal pro Seitentyp prüfen; man nimmt leicht an, der Head stimme, nur weil das Modul installiert ist.

Welche hreflang-Regeln werden oft falsch umgesetzt?

Ein hreflang-ClusterDrei Sprachversionen einer Seite verlinken sich gegenseitig in beide Richtungen, jede listet auch sich selbst, und ein x-default-Link zeigt für nicht passende Sprachen auf die englische Version./blog/posthreflang="en"/de/blog/posthreflang="de"/hu/blog/posthreflang="hu"x-defaultJede Version listet alle drei plus x-default, auch sich selbst.
hreflang funktioniert nur als geschlossener Satz: Verlinkt die deutsche Seite nicht zurück, ignoriert Google das Paar womöglich.

Googles hreflang-Dokumentation ist kurz, und die meisten Fehler, die ich in Audits finde, verletzen eine dieser Regeln:

  • Die Seite selbst einschließen. Jede Version listet einen Link pro Variante, sich selbst eingeschlossen, und der Satz ist auf jeder Version identisch.
  • Vollständige URLs verwenden, mit Protokoll und Host. Relative hrefs sind meist die Folge einer fehlenden baseUrl.
  • Gültige Codes nutzen: eine Sprache nach ISO 639-1, optional gefolgt von einer Region nach ISO 3166-1 (de-AT). Eine Region allein funktioniert nicht, und Codes wie es-419 werden nicht unterstützt.
  • x-default ergänzen für Nutzer, deren Sprache zu keiner Version passt, meist die Seite in der Standardsprache.
  • Den Canonical selbstreferenzierend setzen auf jeder Sprachversion. Eine deutsche Seite, deren Canonical auf die englische zeigt, sagt Google, die deutsche fallen zu lassen.

Noch ein wichtiger Punkt: Google sagt, dass es weder hreflang noch das lang-Attribut zur Spracherkennung nutzt, sondern die Sprache aus dem Inhalt erkennt. Ein übersetztes Template mit unübersetztem Fließtext bleibt eine englische Seite.

Was ist mit Seiten, die nur in manchen Sprachen existieren?

Mit prefix_except_default erzeugt Nuxt für jede Locale eine Route, ein rein englischer Artikel wird also auch unter /de/blog/post mit deutscher Navigation und englischem Text gerendert. Ohne Eingriff bewirbt useLocaleHead ihn als deutsche Übersetzung, und der Canonical der deutschen Kopie zeigt auf sich selbst. Diese Website behebt beides in app.vue: Eine Seite, die in weniger Sprachen existiert, meldet diese an, hreflang listet dann nur sie, und unübersetzte Kopien setzen ihren Canonical aufs englische Original.

// app.vue (excerpt): pageLocales is set by pages that exist in fewer languages
const headLinks = computed(() => {
  const links = i18nHead.value.link ?? []
  const only = pageLocales.value
  if (!only || only.path !== route.path) return links
  return links
    .filter((l) => l.rel !== 'alternate' || !l.hreflang || l.hreflang === 'x-default'
      || only.locales.some((c) => l.hreflang.startsWith(c)))
    .map((l) => (l.rel === 'canonical' && !only.translated
      ? { ...l, href: `${siteUrl}${switchLocalePath('en')}` }
      : l))
})

Die Alternative ist, unübersetzte Routen gar nicht zu erzeugen und einen 404 oder eine Weiterleitung aufs Original zu liefern. Das ist für Suchmaschinen sauberer, aber schlechter für Besucher, die die Oberfläche auf Deutsch gestellt haben und dabei bleiben wollen. Beides funktioniert, solange der Head die Wahrheit darüber sagt, welche Übersetzungen existieren.

Gehört hreflang auch in die Sitemap?

Google akzeptiert hreflang an drei Stellen, in HTML-Tags, HTTP-Headern und der Sitemap, behandelt sie als gleichwertig und sagt, mehrere davon brächten in der Suche keinen Vorteil, erschwerten aber die Pflege. HTTP-Header sind vor allem für Nicht-HTML-Dateien wie PDFs nützlich. Diese Website gibt Head-Tags und Sitemap-Einträge aus, weil beide beim Build aus denselben Beitragsdaten erzeugt werden und so nicht auseinanderlaufen können. Kommen sie bei dir aus verschiedenen Codepfaden, eine Variante wählen.

<url>
  <loc>https://balazscsorba.com/de/blog/agent-loop-explained</loc>
  <xhtml:link rel="alternate" hreflang="en" href="https://balazscsorba.com/blog/agent-loop-explained"/>
  <xhtml:link rel="alternate" hreflang="de" href="https://balazscsorba.com/de/blog/agent-loop-explained"/>
  <xhtml:link rel="alternate" hreflang="hu" href="https://balazscsorba.com/hu/blog/agent-loop-explained"/>
  <xhtml:link rel="alternate" hreflang="x-default" href="https://balazscsorba.com/blog/agent-loop-explained"/>
</url>

Warum nicht nach Browsersprache weiterleiten?

Google rät davon ab, Nutzer anhand der vermuteten Sprache auf eine Sprachversion umzuleiten, weil das Nutzer und Suchmaschinen daran hindern kann, alle Versionen zu erreichen. detectBrowserLanguage von Nuxt i18n kann beim ersten Besuch umleiten; bei einer öffentlichen Website schalte ich es ab und setze stattdessen auf zwei Dinge: hreflang, das Suchende direkt aus der Ergebnisseite zur richtigen Version schickt, und einen Sprachumschalter aus echten Links, dem auch Crawler folgen können.

Was muss für mehrsprachiges SEO noch übersetzt werden?

  • Titel und Meta-Descriptions pro Locale. Sie sind das, was Suchende in den Ergebnissen lesen; eine deutsche Seite mit englischem Titel verliert Klicks.
  • Strukturierte Daten mit inLanguage, passend zur Seite, damit KI-Suche und Rich Results die richtige Version zitieren. Mehr dazu im GEO-Audit dieser Website.
  • Alt-Texte und Open-Graph-Bilder mit lokalisiertem Text, wenn das Bild Wörter enthält.
  • Interne Links, die in der Sprache bleiben. Überall localePath() verwenden, damit ein deutscher Artikel auf deutsche Seiten verlinkt und Besucher nicht ins Englische abfließen.

Performance zählt in jeder Sprache gleich; es gelten dieselben Regeln wie in Core Web Vitals für Nuxt, und Lazy Loading der Nachrichtendateien pro Locale hält das Bundle klein.

Wie prüft man das Setup?

Das generierte HTML einer Seite pro Typ und Sprache abrufen und die Link-Sätze vergleichen; ein kurzes Skript findet die üblichen Fehler, bevor die Search Console es Wochen später tut.

// check-hreflang.mjs: every version must list the same set, including itself
const pages = ['/blog/agent-loop-explained', '/de/blog/agent-loop-explained', '/hu/blog/agent-loop-explained']
const base = 'https://balazscsorba.com'
const sets = await Promise.all(pages.map(async (p) => {
  const html = await (await fetch(base + p)).text()
  const attr = (tag, name) => tag.match(new RegExp(` ${name}="([^"]+)"`))?.[1]
  const tags = html.match(/<link[^>]*>/g) ?? []
  const links = tags.filter((t) => attr(t, 'rel') === 'alternate' && attr(t, 'hreflang'))
    .map((t) => [attr(t, 'hreflang'), attr(t, 'href')])
  const canonical = attr(tags.find((t) => attr(t, 'rel') === 'canonical') ?? '', 'href')
  if (canonical !== base + p) console.warn(`${p}: canonical is ${canonical}`)
  if (!links.some(([, href]) => href === base + p)) console.warn(`${p}: does not list itself`)
  return links.map(([lang, href]) => `${lang} ${href}`).sort().join('\n')
}))
console.log(new Set(sets).size === 1 ? 'hreflang sets match' : 'hreflang sets differ')

Quellen

  1. Nuxt i18n docs: SEO (useLocaleHead)
  2. Nuxt i18n docs: Routing strategies
  3. Google Search Central: Tell Google about localized versions of your page
  4. Google Search Central: Managing multi-regional and multilingual sites
  5. Google Search Central: How to specify a canonical URL

Häufige Fragen

Welche Routing-Strategie von Nuxt i18n ist die beste fürs SEO?

prefix_except_default und prefix geben jeder Sprache eine eigene URL, wie Google es empfiehlt. prefix_except_default lässt die Standardsprache ohne Präfix. no_prefix für öffentliche Inhalte meiden, denn alle Sprachen teilen sich eine URL und Suchmaschinen können nur eine davon indexieren.

Erzeugt useLocaleHead hreflang automatisch?

Ja. Es liefert das lang-Attribut des html-Elements, einen Alternate-Link mit hreflang für jede konfigurierte Locale, og:locale-Tags und einen Canonical-Link. Dafür braucht jede Locale einen language-Wert und das Modul eine baseUrl, damit die URLs vollständig sind.

Brauche ich x-default?

Es wird empfohlen. Google nutzt x-default für Nutzer, deren Sprache zu keiner Version passt. Meist zeigt es auf die Version in der Standardsprache oder auf eine Sprachauswahlseite.

Gehört hreflang in den HTML-Head oder in die Sitemap?

Beides geht. Google behandelt HTML-Tags, HTTP-Header und Sitemap-Einträge als gleichwertig und sagt, dass mehrere davon keinen Zusatznutzen bringen. Die Variante wählen, die man korrekt halten kann; bei statischen Seiten aus einer Datenquelle können beide aus demselben Code kommen.

Nutzt Google hreflang, um die Sprache einer Seite zu erkennen?

Nein. Laut Google wird die Sprache mit eigenen Algorithmen aus dem Inhalt erkannt; hreflang sagt nur, welche URLs lokalisierte Versionen derselben Seite sind. Eine deutsche Seite voller englischem Text ist für Google trotzdem englisch.

Klingt nach dem, was du suchst?

Erzähl mir von deinem Projekt oder deiner Stelle – ich freue mich, von dir zu hören.