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.
Balázs Csorba··8 Min. Lesezeit
- Nuxt
- i18n
- SEO
- hreflang
- Multilingual

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.
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:
| Strategie | URLs | SEO-Urteil |
|---|---|---|
prefix_except_default | /blog, /de/blog, /hu/blog | Guter Standard; die Hauptsprache behält saubere URLs |
prefix | /en/blog, /de/blog, /hu/blog | Gut; symmetrisch, braucht eine Weiterleitung oder Sprachauswahl an der Wurzel |
prefix_and_default | /blog and /en/blog, /de/blog | Funktioniert, erzeugt aber Duplikate; der Canonical zeigt auf die URL ohne Präfix |
no_prefix | /blog for all | Fü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?
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 wiees-419werden nicht unterstützt. x-defaultergä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
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.