hreflang Tags Ultimate Guide: Implementierung für mehrsprachige SPAs

Ein vollständiger Leitfaden zur Implementierung von hreflang-Tags für mehrsprachige Single-Page-Anwendungen, einschließlich dynamischer Tag-Injection, kanonischer URL-Strategien, Sitemap-Generierung und häufiger Fallstricke.

Sam Rivera
Sam Rivera
9. Juni 202613 min read
Auch verfügbar auf:English · 中文

Kurzfassung: hreflang-Tags teilen Suchmaschinen mit, welche Sprachversion einer Seite in den Suchergebnissen angezeigt werden soll. Für Single-Page-Anwendungen (SPAs) erfordert eine korrekte hreflang-Implementierung eine dynamische Tag-Injection über den <head>, konsistente kanonische URL-Muster und sprachspezifische Sitemaps. Dieser Leitfaden deckt alles ab – von der grundlegenden Tag-Syntax bis hin zu fortgeschrittenen Mustern für SPAs, die mit TanStack Router oder React Router erstellt wurden, einschließlich automatischer Spracherkennung, Fallback-Hierarchien und Google Search Console-Verifizierung.


Was sind hreflang-Tags und warum sind sie wichtig?

hreflang ist ein HTML-Attribut, das Suchmaschinen die Beziehung zwischen Seiten in verschiedenen Sprachen mitteilt. Wenn ein Nutzer auf Deutsch nach „SaaS-Preise" sucht, verwendet Google hreflang, um die deutsche Version Ihrer Seite anzuzeigen – selbst wenn die englische Version über eine höhere Domain-Autorität verfügt.

Die korrekte Implementierung von hreflang-Tags ist ein entscheidender Bestandteil jeder technischen Globalisierungsstrategie. Ohne sie kann mehrsprachiger Content unter Duplicate-Content-Strafen, falschen Sprach-Rankings und verschwendetem Crawl-Budget leiden – all das schwächt Ihre internationalen SEO-Bemühungen.

Ohne hreflang treten bei mehrsprachigen Websites drei Probleme auf:

ProblemSEO-AuswirkungLösung
Doppelter ContentGoogle sieht /en/pricing und /de/preise als dieselbe Seite – Ranking-Signale werden verwässerthreflang teilt Google mit, dass es sich um Übersetzungen handelt, nicht um Duplikate
Falsches Sprach-RankingEnglische Seite rankt für deutsches Keyword, was zu einer hohen Absprungrate führthreflang leitet Nutzer an ihre Sprache
Verschwendung des Crawl-BudgetsGooglebot crawlte jede Sprachvariante jeder SeiteKorrekte Annotationen konsolidieren Crawl-Signale

Eine Studie von Aleyda Solis aus dem Jahr 2025 ergab, dass 63 % der mehrsprachigen Websites noch immer eine fehlerhafte oder fehlende hreflang-Implementierung aufweisen – was dies zu einem schnellen SEO-Gewinn für Websites macht, die es richtig umsetzen.


hreflang-Tag-Syntax und -Muster

Grundlegende Syntax

html
<link rel="alternate" hreflang="en" href="https://tanstackship.com/en/page" />
<link rel="alternate" hreflang="de" href="https://tanstackship.com/de/page" />
<link rel="alternate" hreflang="zh" href="https://tanstackship.com/zh/page" />
<link rel="alternate" hreflang="x-default" href="https://tanstackship.com/en/page" />

Zwei Implementierungsmethoden

MethodeWoVorteileNachteile
HTML-<link>-TagsIm <head> jeder SeiteEinfach, funktioniert überallErfordert dynamische Injection pro Seite
XML-SitemapsIn sitemap.xml mit xhtml:linkZentrale Verwaltung, keine HTML-ÄnderungenGoogle kann länger brauchen, um Seiten zu entdecken

Best Practice: Beides verwenden. HTML-Tags für schnelle Entdeckung, Sitemaps für Vollständigkeit.


hreflang in SPAs implementieren: Die Herausforderung

SPAs rendern JavaScript clientseitig, was bedeutet, dass statische HTML-Dateien keine dynamischen hreflang-Tags enthalten können. Es gibt drei Ansätze:

Ansatz 1: Serverseitige Injection (Empfohlen für TanStack Start)

Mit dem SSR von TanStack Start können hreflang-Tags serverseitig injiziert werden, bevor das HTML den Client erreicht. Dieser Ansatz stellt sicher, dass Suchmaschinen-Crawler vollständige hreflang-Markups in der ersten Antwort erhalten, wodurch die mit clientseitiger Hydrierung verbundenen SEO-Risiken vermieden werden. Eine umfassende Anleitung zum Erstellen einer Full-Stack-Anwendung mit SSR finden Sie im TanStack Start-Tutorial.

Der folgende Code zeigt das hreflang-Generierungsutility und seine Integration in die Root-Route:

typescript
// src/lib/hreflang.ts
interface HreflangConfig {
  baseUrl: string
  currentPath: string
  locales: string[]
  defaultLocale: string
}

export function generateHreflangLinks(config: HreflangConfig): string[] {
  const { baseUrl, currentPath, locales, defaultLocale } = config

  // Normalize path: strip existing locale prefix if present
  const pathWithoutLocale = currentPath.replace(/^\/(en|de|zh)/, "")

  return [
    // One link per locale
    ...locales.map(
      (locale) =>
        `<link rel="alternate" hreflang="${locale}" href="${baseUrl}/${locale}${pathWithoutLocale}" />`
    ),
    // x-default fallback
    `<link rel="alternate" hreflang="x-default" href="${baseUrl}/${defaultLocale}${pathWithoutLocale}" />`,
  ]
}
typescript
// src/routes/__root.tsx — inject hreflang via SSR
import { Head } from "@tanstack/react-start"
import { generateHreflangLinks } from "../lib/hreflang"

export const Route = createRootRoute({
  component: () => {
    const { locale } = Route.useRouteContext()

    // Strip leading slash for sitemap path
    const path = location.pathname
    const hreflangLinks = generateHreflangLinks({
      baseUrl: "https://tanstackship.com",
      currentPath: path,
      locales: ["en", "de", "zh"],
      defaultLocale: "en",
    })

    return (
      <html lang={locale}>
        <head>
          <Head />
          <meta property="og:locale" content={locale === "zh" ? "zh_CN" : locale === "de" ? "de_DE" : "en_US"} />
          {hreflangLinks.map((link) => (
            <link
              key={link.match(/hreflang="([^"]+)"/)?.[1] || Math.random()}
              rel="alternate"
              hrefLang={link.match(/hreflang="([^"]+)"/)?.[1]}
              href={link.match(/href="([^"]+)"/)?.[1]}
            />
          ))}
        </head>
        <body>
          <Outlet />
        </body>
      </html>
    )
  },
})

Die Funktion generateHreflangLinks normalisiert den aktuellen Pfad, indem sie ein vorhandenes Locale-Präfix entfernt, und generiert dann ein <link>-Tag pro unterstützter Locale plus ein x-default-Fallback. Die Root-Route-Komponente rendert diese Tags als JSX im <head> und gewährleistet so, dass jede servergelieferte Seite vollständige hreflang-Annotationen enthält.

Ansatz 2: Statische Site-Generierung (Vorgängenerstellt pro Locale)

Für statisch generierte SPAs: Generieren Sie eine HTML-Datei pro Locale pro Seite:

typescript
// scripts/generate-hreflang.ts
import fs from "fs/promises"
import path from "path"

interface PageConfig {
  path: string
  locales: string[]
}

async function injectHreflang(page: PageConfig) {
  const { path: pagePath, locales } = page
  const baseUrl = "https://tanstackship.com"

  const hreflangTags = locales
    .map(
      (locale) =>
        `<link rel="alternate" hreflang="${locale}" href="${baseUrl}/${locale}${pagePath}" />`
    )
    .join("\n")

  // Inject into each locale's HTML file
  for (const locale of locales) {
    const filePath = path.join("dist", locale, pagePath, "index.html")
    const html = await fs.readFile(filePath, "utf-8")

    const updatedHtml = html.replace(
      "</head>",
      `${hreflangTags}\n<link rel="alternate" hreflang="x-default" href="${baseUrl}/${locale}${pagePath}" />\n</head>`
    )

    await fs.writeFile(filePath, updatedHtml)
  }
}

Ansatz 3: Clientseitige dynamische Injection (Am wenigsten empfohlen)

Als letztes Mittel für rein clientseitige SPAs: Injizieren Sie Tags nach dem ersten Rendern:

typescript
// src/hooks/useHreflang.ts
import { useEffect } from "react"
import { useLocation } from "@tanstack/react-router"

export function useHreflang(locales: string[]) {
  const location = useLocation()
  const baseUrl = "https://tanstackship.com"

  useEffect(() => {
    const path = location.pathname.replace(/^\/(en|de|zh)/, "")

    // Remove existing hreflang tags
    document
      .querySelectorAll('link[rel="alternate"][hreflang]')
      .forEach((el) => el.remove())

    // Inject new ones
    locales.forEach((locale) => {
      const link = document.createElement("link")
      link.rel = "alternate"
      link.setAttribute("hreflang", locale)
      link.href = `${baseUrl}/${locale}${path}`
      document.head.appendChild(link)
    })

    // x-default
    const defaultLink = document.createElement("link")
    defaultLink.rel = "alternate"
    defaultLink.setAttribute("hreflang", "x-default")
    defaultLink.href = `${baseUrl}/${locales[0]}${path}`
    document.head.appendChild(defaultLink)
  }, [location.pathname, locales])
}

Warnung: Die clientseitige Injection ist für SEO problematisch – Googlebot wartet möglicherweise nicht auf die JavaScript-Ausführung, bevor es den <head> liest. Verwenden Sie nach Möglichkeit die serverseitige Injection.


Sitemap-basiertes hreflang (Der vollständige Weg)

XML-Sitemaps mit xhtml:link-Annotationen bieten ein Fallback für Seiten, die Googlebot möglicherweise übersieht:

xml
<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"
        xmlns:xhtml="http://www.w3.org/1999/xhtml">
  <url>
    <loc>https://tanstackship.com/en/pricing</loc>
    <xhtml:link rel="alternate" hreflang="en" href="https://tanstackship.com/en/pricing"/>
    <xhtml:link rel="alternate" hreflang="de" href="https://tanstackship.com/de/preise"/>
    <xhtml:link rel="alternate" hreflang="zh" href="https://tanstackship.com/zh/pricing"/>
    <xhtml:link rel="alternate" hreflang="x-default" href="https://tanstackship.com/en/pricing"/>
    <lastmod>2026-06-09</lastmod>
  </url>
  <url>
    <loc>https://tanstackship.com/de/preise</loc>
    <xhtml:link rel="alternate" hreflang="en" href="https://tanstackship.com/en/pricing"/>
    <xhtml:link rel="alternate" hreflang="de" href="https://tanstackship.com/de/preise"/>
    <xhtml:link rel="alternate" hreflang="zh" href="https://tanstackship.com/zh/pricing"/>
    <xhtml:link rel="alternate" hreflang="x-default" href="https://tanstackship.com/en/pricing"/>
    <lastmod>2026-06-09</lastmod>
  </url>
</urlset>

Mehrsprachige Sitemaps mit TanStack Start generieren

typescript
// src/server/sitemap.ts
import { createServerFn } from "@tanstack/react-start"

interface SitemapUrl {
  path: string
  updatedAt: string
  locale: string
}

export const generateSitemap = createServerFn({ method: "GET" }).handler(
  async () => {
    const baseUrl = "https://tanstackship.com"
    const locales = ["en", "de", "zh"]

    // Get all published blog posts with their translations
    const posts = await env.DB.prepare(
      `SELECT b.slug, b.updated_at, b.locale
       FROM blog_posts b
       WHERE b.published = 1`
    ).all()

    // Build URL groups (same content, different languages)
    const urlGroups = buildUrlGroups(posts.results as SitemapUrl[], baseUrl)

    return generateSitemapXml(urlGroups, locales, baseUrl)
  }
)

Häufige hreflang-Fehler (und wie Sie diese beheben)

Fehler 1: Fehlende Return-Tags

Wenn Seite A über hreflang auf Seite B verlinkt, muss Seite B zurück auf Seite A verlinken. Google nennt dies „bidirektionale Annotation".

html
<!-- ❌ FALSCH: Seite A verlinkt auf B, aber B verlinkt nicht zurück -->
<!-- Seite A: -->
<link rel="alternate" hreflang="de" href="/de/page" />
<!-- Seite B: -->
<!-- Keinerlei hreflang-Tags! -->

<!-- ✅ RICHTIG: Bidirektionale Verlinkung -->
<!-- Seite A: -->
<link rel="alternate" hreflang="en" href="/en/page" />
<link rel="alternate" hreflang="de" href="/de/page" />
<!-- Seite B: -->
<link rel="alternate" hreflang="en" href="/en/page" />
<link rel="alternate" hreflang="de" href="/de/page" />

Fehler 2: Inkonsistente Sprachcodes

html
<!-- ❌ FALSCH: Gemischte ISO-Formate -->
<link rel="alternate" hreflang="english" href="/en/page" />
<link rel="alternate" hreflang="de-de" href="/de/page" />

<!-- ✅ RICHTIG: ISO 639-1-Sprachcodes mit optionaler ISO 3166-1-Region -->
<link rel="alternate" hreflang="en" href="/en/page" />
<link rel="alternate" hreflang="en-US" href="/en/page" />
<link rel="alternate" hreflang="de" href="/de/page" />
<link rel="alternate" hreflang="de-DE" href="/de/page" />
<!-- De-duplizieren: Wenn zwei hreflang-Werte auf dieselbe URL verweisen, verwenden Sie die spezifischere -->

Fehler 3: Kanonische URLs im Konflikt

html
<!-- ❌ FALSCH: Kanonisch verweist auf eine andere Locale als hreflang -->
<link rel="canonical" href="/en/page" />
<link rel="alternate" hreflang="de" href="/de/page" />

<!-- ✅ RICHTIG: Selbstreferenzierendes Kanonisch -->
<link rel="canonical" href="/de/page" />
<link rel="alternate" hreflang="en" href="/en/page" />
<link rel="alternate" hreflang="de" href="/de/page" />

Fehler 4: hreflang mit Fragment-Identifiers verwenden

html
<!-- ❌ FALSCH: Google ignoriert Fragmente -->
<link rel="alternate" hreflang="de" href="/page#section2" />

<!-- ✅ RICHTIG: Vollständige URL ohne Fragment verwenden -->
<link rel="alternate" hreflang="de" href="/de/page" />

Das x-default hreflang: Wann es verwendet werden sollte

x-default teilt Google mit, welche Seite angezeigt werden soll, wenn keine Sprache den Nutzerpräferenzen entspricht. Dies ist für SPAs entscheidend, da die Browserspracherkennung möglicherweise nicht mit Ihren unterstützten Locales übereinstimmt.

html
<!-- x-default sollte auf Ihren häufigsten Einstiegspunkt verweisen -->
<link rel="alternate" hreflang="x-default" href="https://tanstackship.com/en" />

<!-- Für ein globales SaaS-Produkt mit Englisch als Standard -->
<link rel="alternate" hreflang="x-default" href="https://tanstackship.com/en" />

<!-- Für ein regionales Produkt: Verweis auf den wahrscheinlichsten Markt -->
<link rel="alternate" hreflang="x-default" href="https://tanstackship.com/de" />

Verwenden Sie x-default, wenn:

  • Die Browsersprache des Nutzers keiner Ihrer unterstützten Sprachen entspricht
  • Der Nutzer ein Suchmaschinen-Crawler ist (Googlebot setzt typischerweise en oder en-us)
  • Sie eine Sprachauswahl-Landingpage wünschen (2026 weniger üblich – Nutzer bevorzugen den direkten Einstieg)

Testen und Validieren

Google Search Console

  1. Navigieren Sie in der Search Console zu Internationales Targeting > Sprache
  2. Prüfen Sie den Bericht auf Fehler wie „keine Return-Tags" und „nicht eindeutige hreflang"
  3. Verwenden Sie das URL-Prüftool, um hreflang auf bestimmten Seiten zu verifizieren

Automatisiertes Testskript

bash
#!/bin/bash
# hreflang-test.sh — Validate hreflang tags on a set of URLs

BASE_URL="https://tanstackship.com"
LOCALES=("en" "de" "zh")
PAGES=("/pricing" "/features" "/blog/react-internationalization-guide-2026")

for page in "${PAGES[@]}"; do
  for locale in "${LOCALES[@]}"; do
    url="${BASE_URL}/${locale}${page}"
    echo "Testing: $url"

    # Fetch and extract hreflang tags
    tags=$(curl -s "$url" | grep -o 'rel="alternate"[^>]*hreflang="[^"]*"[^>]*' | wc -l)
    echo "  Found $tags hreflang tags"

    # Check for return links
    has_return=$(curl -s "$url" | grep -c "hreflang=\"${locale}\"")
    if [ "$has_return" -eq 0 ]; then
      echo "  ⚠️  Missing self-referencing hreflang for ${locale}"
    fi
  done
done

Online-Validatoren


hreflang für dynamische Inhalte (Blogbeiträge, CMS-Seiten)

Für Content-getriebene SPAs mit Blogbeiträgen in mehreren Sprachen müssen hreflang-Tags dynamisch auf Basis von Datenbankbeziehungen zwischen übersetzten Inhalten generiert werden. Wenn Sie ein mehrsprachiges Content-System implementieren, deckt unser React-Internationalisierungsleitfaden die breitere i18n-Architektur für SPAs ab, die alongside diesen hreflang-Mustern funktioniert.

Das folgende Utility ruft alle Übersetzungen für einen bestimmten Blogbeitrag ab und generiert die entsprechenden hreflang-Link-Tags:

typescript
// src/lib/blog-hreflang.ts
interface BlogPostTranslation {
  locale: string
  slug: string
}

export async function getBlogPostHreflang(
  postId: string,
  currentLocale: string
): Promise<string[]> {
  const translations = await env.DB.prepare(
    `SELECT locale, slug FROM blog_posts
     WHERE translation_group_id = (
       SELECT translation_group_id FROM blog_posts WHERE id = ?
     ) AND published = 1`
  )
    .bind(postId)
    .all()

  return (translations.results as BlogPostTranslation[]).map(
    (t) =>
      `<link rel="alternate" hreflang="${t.locale}" href="https://tanstackship.com/${t.locale}/blog/${t.slug}" />`
  )
}
sql
-- Database schema for translated blog posts
CREATE TABLE blog_posts (
  id TEXT PRIMARY KEY,
  translation_group_id TEXT NOT NULL,
  locale TEXT NOT NULL,
  slug TEXT NOT NULL,
  title TEXT NOT NULL,
  content TEXT NOT NULL,
  published INTEGER NOT NULL DEFAULT 0,
  created_at INTEGER NOT NULL DEFAULT (unixepoch()),
  updated_at INTEGER NOT NULL DEFAULT (unixepoch()),
  UNIQUE(locale, slug)
);

CREATE INDEX idx_blog_posts_translation_group
  ON blog_posts(translation_group_id);

Das Datenbankschema speichert jeden Blogbeitrag mit einer translation_group_id, die alle Sprachvarianten desselben Contents gruppiert. Die Funktion getBlogPostHreflang fragt diese Gruppe ab, um vollständige hreflang-Annotationen zu generieren, und stellt sicher, dass jeder Blogbeitrag unabhängig davon, in welcher Sprache der Nutzer ihn betrachtet, korrekt auf seine übersetzten Gegenstücke verweist.


Leistungsauswirkung: Beeinflusst hreflang die Seitengeschwindigkeit?

hreflang-Tags sind <link>-Elemente im <head> – sie blockieren weder das Rendern noch fügen sie messbares Gewicht hinzu. Für eine typische SaaS-Site mit 3–5 Locales beträgt das gesamte hreflang-Markup weniger als 1 KB. Die eigentliche Leistungsbetrachtung betrifft die serverseitige Injection: Das Generieren von hreflang-Tags zur Anfragezeit fügt einen vernachlässigbaren Overhead hinzu (< 1 ms) im Vergleich zu den Vorteilen korrekter internationaler SEO.


Checkliste: hreflang für die Produktionsreife von SPAs

  • Jede Seite hat selbstreferenzierendes hreflang (verlinkt auf ihre eigene Locale)
  • Jede Seite hat kreuzreferenzierendes hreflang (verlinkt auf alle anderen Locales)
  • Return-Tags sind bidirektional verifiziert (Seite A ↔ Seite B für alle Paare)
  • x-default hreflang ist auf allen Seiten gesetzt
  • Kanonische URLs sind pro Locale selbstreferenzierend
  • Sprachcodes verwenden das gültige ISO 639-1-Format
  • Sitemaps enthalten xhtml:link-Annotationen für alle Locale-Varianten
  • Google Search Console zeigt keine hreflang-Fehler
  • Dynamische Seiten (Blogbeiträge) generieren hreflang aus Datenbank-Übersetzungsgruppen
  • Sprachwechsler aktualisiert hreflang-Tags ohne vollständigen Seitenreload
  • Keine widersprüchlichen hreflang-Deklarationen in robots.txt oder HTTP-Headern
  • Mit einem echten mehrsprachigen Nutzerflow in allen unterstützten Sprachen getestet

Fazit

Die hreflang-Implementierung für SPAs erfordert mehr Aufmerksamkeit als für traditionelle Multi-Page-Sites, da dynamisches clientseitiges Rendern die Tag-Entdeckung verzögern oder verhindern kann. Die wichtigsten Prinzipien sind:

  1. hreflang serverseitig injizieren, wann immer möglich – das SSR von TanStack Start macht dies unkompliziert
  2. Immer Rückverweise einschließen – bidirektionale Annotationen sind eine zwingende Anforderung, keine Empfehlung
  3. Sowohl HTML-Tags als auch Sitemaps verwenden – Sitemaps dienen als Fallback für Seiten, die Googlebot nicht vollständig rendert
  4. Kanonische URLs pro Locale selbstreferenzierend halten, um widersprüchliche Signale zu vermeiden
  5. Tests automatisieren – manuelle hreflang-Verifizierung skaliert nicht über eine Handvoll Seiten hinaus

Mehrsprachige SEO ist eine langfristige Investition. Eine korrekte hreflang-Implementierung stellt sicher, dass Ihr Content in jedem Sprachmarkt, den Sie anvisieren, konkurriert – ohne die Belastung durch Duplicate-Content-Strafen oder falsche Sprach-Rankings.