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.