Skip to content

Internationalization (hreflang)

Use hreflang annotations to tell Google about alternate language versions of your pages. This helps serve the correct language version to users in different regions.

Basic Usage

Add an alternates array to any route:

typescript
import type { Route } from "@pyyupsk/vite-plugin-sitemap";

export default [
  {
    url: "/products",
    alternates: [
      { hreflang: "en", href: "https://example.com/products" },
      { hreflang: "es", href: "https://example.com/es/productos" },
      { hreflang: "fr", href: "https://example.com/fr/produits" },
    ],
  },
] satisfies Route[];

Alternate Properties

typescript
interface Alternate {
  hreflang: string;
  href: string;
}

hreflang

Language and optional region code:

typescript
// Language only (ISO 639-1)
{
  hreflang: "en";
}
{
  hreflang: "es";
}
{
  hreflang: "fr";
}

// Language and region (ISO 639-1 + ISO 3166-1 Alpha 2)
{
  hreflang: "en-US";
}
{
  hreflang: "en-GB";
}
{
  hreflang: "es-MX";
}
{
  hreflang: "zh-TW";
}

// Default fallback
{
  hreflang: "x-default";
}

href

The URL of the alternate version (must be absolute):

typescript
{
  href: "https://example.com/es/productos";
}

Full Example

typescript
import type { Route } from "@pyyupsk/vite-plugin-sitemap";

const languages = ["en", "es", "fr", "de", "ja"] as const;

export default [
  // English version
  {
    url: "https://example.com/products",
    alternates: [
      { hreflang: "en", href: "https://example.com/products" },
      { hreflang: "es", href: "https://example.com/es/productos" },
      { hreflang: "fr", href: "https://example.com/fr/produits" },
      { hreflang: "de", href: "https://example.com/de/produkte" },
      { hreflang: "ja", href: "https://example.com/ja/products" },
      { hreflang: "x-default", href: "https://example.com/products" },
    ],
  },
  // Spanish version
  {
    url: "https://example.com/es/productos",
    alternates: [
      { hreflang: "en", href: "https://example.com/products" },
      { hreflang: "es", href: "https://example.com/es/productos" },
      { hreflang: "fr", href: "https://example.com/fr/produits" },
      { hreflang: "de", href: "https://example.com/de/produkte" },
      { hreflang: "ja", href: "https://example.com/ja/products" },
      { hreflang: "x-default", href: "https://example.com/products" },
    ],
  },
  // ... other language versions
] satisfies Route[];

Generated XML

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://example.com/products</loc>
    <xhtml:link rel="alternate" hreflang="en" href="https://example.com/products"/>
    <xhtml:link rel="alternate" hreflang="es" href="https://example.com/es/productos"/>
    <xhtml:link rel="alternate" hreflang="fr" href="https://example.com/fr/produits"/>
    <xhtml:link rel="alternate" hreflang="de" href="https://example.com/de/produkte"/>
    <xhtml:link rel="alternate" hreflang="ja" href="https://example.com/ja/products"/>
    <xhtml:link rel="alternate" hreflang="x-default" href="https://example.com/products"/>
  </url>
</urlset>

Dynamic Multi-Language Routes

Generate routes for all language versions automatically:

typescript
import type { Route } from "@pyyupsk/vite-plugin-sitemap";

const hostname = "https://example.com";

const languages = [
  { code: "en", prefix: "" },
  { code: "es", prefix: "/es" },
  { code: "fr", prefix: "/fr" },
  { code: "de", prefix: "/de" },
] as const;

const pages = [
  { path: "/products", translations: { es: "/productos", fr: "/produits", de: "/produkte" } },
  { path: "/about", translations: { es: "/acerca", fr: "/a-propos", de: "/uber-uns" } },
  { path: "/contact", translations: { es: "/contacto", fr: "/contact", de: "/kontakt" } },
];

function getLocalizedPath(page: (typeof pages)[0], lang: (typeof languages)[0]): string {
  if (lang.code === "en") return page.path;
  return page.translations[lang.code as keyof typeof page.translations] || page.path;
}

function generateAlternates(page: (typeof pages)[0]) {
  return [
    ...languages.map((lang) => ({
      hreflang: lang.code,
      href: `${hostname}${lang.prefix}${getLocalizedPath(page, lang)}`,
    })),
    { hreflang: "x-default", href: `${hostname}${page.path}` },
  ];
}

export default languages.flatMap((lang) =>
  pages.map((page) => ({
    url: `${lang.prefix}${getLocalizedPath(page, lang)}`,
    alternates: generateAlternates(page),
  })),
) satisfies Route[];

Region-Specific Variants

For content that varies by region within the same language:

typescript
import type { Route } from "@pyyupsk/vite-plugin-sitemap";

export default [
  {
    url: "/pricing",
    alternates: [
      // US English (default)
      { hreflang: "en-US", href: "https://example.com/pricing" },
      // UK English (different prices/currency)
      { hreflang: "en-GB", href: "https://example.co.uk/pricing" },
      // Australian English
      { hreflang: "en-AU", href: "https://example.com.au/pricing" },
      // Canadian English
      { hreflang: "en-CA", href: "https://example.ca/pricing" },
      // Canadian French
      { hreflang: "fr-CA", href: "https://example.ca/fr/tarification" },
      // Default fallback
      { hreflang: "x-default", href: "https://example.com/pricing" },
    ],
  },
] satisfies Route[];

Using with CMS Data

typescript
import type { Route } from "@pyyupsk/vite-plugin-sitemap";

interface Page {
  slug: string;
  locale: string;
  translations: Array<{ locale: string; slug: string }>;
}

export default async function getRoutes(): Promise<Route[]> {
  const pages: Page[] = await fetch("https://cms.example.com/pages").then((r) => r.json());

  return pages.map((page) => ({
    url: `/${page.locale}/${page.slug}`,
    alternates: [
      { hreflang: page.locale, href: `https://example.com/${page.locale}/${page.slug}` },
      ...page.translations.map((t) => ({
        hreflang: t.locale,
        href: `https://example.com/${t.locale}/${t.slug}`,
      })),
      { hreflang: "x-default", href: `https://example.com/en/${page.slug}` },
    ],
  }));
}

Best Practices

  1. Include all alternates - Every language version should list all other versions
  2. Self-reference - Include the current page in its own alternates list
  3. Use x-default - Provide a fallback for users whose language isn't listed
  4. Consistent URLs - Use the same URL structure across languages
  5. Bidirectional links - If page A links to page B, page B must link back to A
  6. Absolute URLs - All href values must be absolute URLs

Common Language Codes

CodeLanguage
enEnglish
esSpanish
frFrench
deGerman
itItalian
ptPortuguese
jaJapanese
zhChinese
koKorean
arArabic

Common Region Codes

CodeRegion
en-USEnglish (United States)
en-GBEnglish (United Kingdom)
es-ESSpanish (Spain)
es-MXSpanish (Mexico)
pt-BRPortuguese (Brazil)
zh-CNChinese (Simplified)
zh-TWChinese (Traditional)

Released under the MIT License.