SEO

hreflang Tags: The Complete Implementation Guide for Multilingual SEO

Eray Gündoğmuş
Eray Gündoğmuş
·12 min read
Share
hreflang Tags: The Complete Implementation Guide for Multilingual SEO

hreflang Tags: The Complete Implementation Guide for Multilingual SEO

If you run a website in more than one language or target users in different countries, hreflang tags are the single most important technical SEO signal you can implement. Get them right and Google serves the correct language version to each user. Get them wrong and you face duplicate content penalties, cannibalisation between your own pages, and rankings dropping in regions you are actively trying to serve.

This guide walks through the full picture: what hreflang tags actually do, the three ways to implement them, every common pattern you will encounter in the wild, framework-specific code snippets for modern JavaScript stacks, the mistakes that break implementations silently, and the tools you need to validate everything before you ship.


TL;DR / Key Takeaways

  • hreflang is an HTML attribute that tells Google which language or regional variant of a page to serve for a given audience. Bing does not use hreflang — it relies on other signals such as Content-Language HTTP headers and geolocation data.
  • Every page in an hreflang set must include a self-referencing tag pointing to itself, in addition to tags pointing to all other variants.
  • hreflang relationships must be bidirectional: if page A declares page B as a variant, page B must also declare page A as a variant. One-way declarations are ignored.
  • The x-default value is a fallback designator, not a language code. Use it for pages that act as a language selector, a redirect gateway, or a catch-all for unmatched locales.
  • hreflang can be implemented in three places: HTML <link> tags in <head>, HTTP response headers, or XML sitemaps. All three are equally valid for Google; choose based on what your stack makes easiest.

What Are hreflang Tags?

hreflang tags are HTML annotations that tell search engines which version of a page is intended for users speaking a particular language or located in a particular country. The attribute was introduced by Google in 2011 and is documented in Google's Search Central developer documentation under the name "Tell Google about localized versions of your page."

The core problem hreflang solves is disambiguation. Suppose your website exists in English at /en/, in German at /de/, and in German targeted specifically at Austrian users at /de-at/. Without hreflang, a search engine's crawler sees three pages with substantially similar content and must guess which one to rank for which audience. It often guesses wrong. With hreflang, you remove the guesswork and explicitly declare the relationship.

How Google Uses hreflang

When Google encounters hreflang annotations, it uses them to group pages into equivalence sets. Within a set, Google selects the most appropriate variant for each search query based on the user's interface language and, to a lesser extent, their location. The selected variant is what appears in search results for that user. hreflang does not guarantee ranking — it only tells Google which page to prefer for a given audience once the page already has ranking signals.

hreflang also resolves duplicate content. When Google sees two URLs with identical or near-identical content and valid hreflang links connecting them, it treats them as intentional regional variants rather than accidental duplicates. This prevents the ranking dilution that occurs when Google consolidates duplicate pages and picks the "wrong" canonical.

Google vs Bing: An Important Distinction

hreflang is a Google (and Yandex) feature. Bing explicitly does not use hreflang tags. According to Bing Webmaster Guidelines, Bing uses the following signals to determine the target language and region of a page:

  • The Content-Language HTTP response header
  • The lang attribute on the <html> element
  • The geographic location of the server
  • The country-code top-level domain (ccTLD), where applicable
  • The content of the page itself

If Bing is a meaningful traffic source for your international audiences, you need to implement those signals independently of your hreflang setup. A well-built multilingual site typically implements both: hreflang for Google and the lang attribute on <html> plus Content-Language headers for Bing. The two approaches do not conflict.


Syntax and Placement

Language and Region Code Format

hreflang values follow the format defined by IETF BCP 47. In practice this means:

  • Language only: en, de, fr, ja, zh-Hans, zh-Hant
  • Language plus region: en-US, en-GB, de-AT, fr-CA, pt-BR
  • Special value: x-default

Language codes are ISO 639-1 two-letter codes. Region codes are ISO 3166-1 alpha-2 two-letter country codes. The separator is a hyphen, not an underscore. A common mistake is writing en_US (underscore) instead of en-US (hyphen) — Google ignores the underscore variant entirely.

This is the most widely used method. Place <link> elements inside the <head> of every page in the hreflang set.

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8" />
  <title>Product Page</title>

  <!-- Self-referencing tag (required) -->
  <link rel="alternate" hreflang="en" href="https://example.com/en/product/" />

  <!-- Other language variants -->
  <link rel="alternate" hreflang="de" href="https://example.com/de/product/" />
  <link rel="alternate" hreflang="fr" href="https://example.com/fr/product/" />
  <link rel="alternate" hreflang="de-AT" href="https://example.com/de-at/product/" />

  <!-- Fallback for unmatched locales -->
  <link rel="alternate" hreflang="x-default" href="https://example.com/product/" />
</head>
<body>
  <!-- page content -->
</body>
</html>

Every language variant of this page must include this exact same set of <link> tags. The German page at /de/product/ must also have all five <link> elements, including the one pointing to /en/product/.

Method 2: HTTP Response Headers

For non-HTML resources — PDFs being the most common case — you cannot embed HTML tags. Instead, the web server emits hreflang as part of the HTTP Link response header. This is also useful for dynamically generated pages where injecting <head> elements is architecturally inconvenient.

HTTP/1.1 200 OK
Content-Type: text/html; charset=UTF-8
Link: <https://example.com/en/product/>; rel="alternate"; hreflang="en",
      <https://example.com/de/product/>; rel="alternate"; hreflang="de",
      <https://example.com/fr/product/>; rel="alternate"; hreflang="fr",
      <https://example.com/product/>; rel="alternate"; hreflang="x-default"

In an Nginx configuration this looks like:

location /en/product/ {
    add_header Link '<https://example.com/en/product/>; rel="alternate"; hreflang="en", <https://example.com/de/product/>; rel="alternate"; hreflang="de", <https://example.com/fr/product/>; rel="alternate"; hreflang="fr"';
}

HTTP headers are parsed before the HTML body, which makes them slightly more efficient for crawlers, but the practical ranking difference is negligible.

Method 3: XML Sitemap

For large sites with thousands of URLs, maintaining hreflang tags on every individual page is operationally expensive. XML sitemaps provide a centralised alternative. Google's sitemap specification supports hreflang via the xhtml:link element inside each <url> block.

<?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">

  <!-- English variant -->
  <url>
    <loc>https://example.com/en/product/</loc>
    <xhtml:link rel="alternate" hreflang="en"       href="https://example.com/en/product/" />
    <xhtml:link rel="alternate" hreflang="de"       href="https://example.com/de/product/" />
    <xhtml:link rel="alternate" hreflang="fr"       href="https://example.com/fr/product/" />
    <xhtml:link rel="alternate" hreflang="x-default" href="https://example.com/product/" />
  </url>

  <!-- German variant — same set of xhtml:link elements -->
  <url>
    <loc>https://example.com/de/product/</loc>
    <xhtml:link rel="alternate" hreflang="en"       href="https://example.com/en/product/" />
    <xhtml:link rel="alternate" hreflang="de"       href="https://example.com/de/product/" />
    <xhtml:link rel="alternate" hreflang="fr"       href="https://example.com/fr/product/" />
    <xhtml:link rel="alternate" hreflang="x-default" href="https://example.com/product/" />
  </url>

  <!-- French variant — same set of xhtml:link elements -->
  <url>
    <loc>https://example.com/fr/product/</loc>
    <xhtml:link rel="alternate" hreflang="en"       href="https://example.com/en/product/" />
    <xhtml:link rel="alternate" hreflang="de"       href="https://example.com/de/product/" />
    <xhtml:link rel="alternate" hreflang="fr"       href="https://example.com/fr/product/" />
    <xhtml:link rel="alternate" hreflang="x-default" href="https://example.com/product/" />
  </url>

</urlset>

The bidirectionality requirement applies here too: every URL in the set must appear as a <url> entry, and each entry must list the full set of xhtml:link elements including a self-reference.


Common hreflang Patterns

Same Language, Different Regions

Use language-plus-region tags when you have meaningfully different content for the same language across countries — different pricing, different legal copy, different product availability.

<!-- United States English -->
<link rel="alternate" hreflang="en-US" href="https://example.com/us/checkout/" />

<!-- United Kingdom English -->
<link rel="alternate" hreflang="en-GB" href="https://example.com/uk/checkout/" />

<!-- Australian English -->
<link rel="alternate" hreflang="en-AU" href="https://example.com/au/checkout/" />

<!-- Fallback for all other English speakers -->
<link rel="alternate" hreflang="en" href="https://example.com/en/checkout/" />

If the differences are trivial (a currency symbol or a phone number format), the overhead of maintaining separate URLs may not justify the SEO benefit. A single en tag with localised content served dynamically is often simpler.

The x-default Tag

x-default designates the page that Google should show to users whose language does not match any of your declared variants. It is also used for pages that function as a language selector or regional gateway.

<!-- Language-specific variants -->
<link rel="alternate" hreflang="en" href="https://example.com/en/" />
<link rel="alternate" hreflang="de" href="https://example.com/de/" />
<link rel="alternate" hreflang="ja" href="https://example.com/ja/" />

<!-- x-default: shown when no variant matches the user's language -->
<link rel="alternate" hreflang="x-default" href="https://example.com/" />

x-default is not a language code. Do not use it as a synonym for English. If your root URL at example.com/ is a JavaScript-driven language selector that redirects users to the appropriate locale, that is the correct target for x-default. If your site defaults to English and you want English users who do not match a regional variant to land on /en/, point x-default to /en/ and also include a separate hreflang="en" tag pointing to the same URL.

Self-Referencing Requirement

Every page in an hreflang set must include a tag that points to itself. This is not optional. Google's documentation states this explicitly: "Each language version must list itself as well as all other language versions."

<!-- On the /de/ page, the self-reference is the hreflang="de" tag -->
<link rel="alternate" hreflang="de" href="https://example.com/de/about/" />
<link rel="alternate" hreflang="en" href="https://example.com/en/about/" />
<link rel="alternate" hreflang="fr" href="https://example.com/fr/about/" />

Without the self-reference, Google may choose a different canonical from the set or disregard the hreflang declaration entirely for that page.

If page A declares page B as an alternate, page B must declare page A as an alternate. Without this, Google cannot verify the relationship and will ignore the annotations. This is the most common silent failure in hreflang implementations: one side of the relationship is missing because the template for that language variant was never updated.


Next.js (App Router)

In the Next.js App Router, use the alternates property in the generateMetadata function. Next.js renders these as <link rel="alternate"> tags in the document <head>.

// app/[locale]/product/[slug]/page.tsx
import type { Metadata } from 'next';

type Props = {
  params: { locale: string; slug: string };
};

export async function generateMetadata({ params }: Props): Promise<Metadata> {
  const { locale, slug } = params;
  const baseUrl = 'https://example.com';

  return {
    alternates: {
      canonical: `${baseUrl}/${locale}/product/${slug}/`,
      languages: {
        'en': `${baseUrl}/en/product/${slug}/`,
        'de': `${baseUrl}/de/product/${slug}/`,
        'fr': `${baseUrl}/fr/product/${slug}/`,
        'x-default': `${baseUrl}/en/product/${slug}/`,
      },
    },
  };
}

Next.js serialises the languages object keys directly as hreflang attribute values, so use BCP 47 format (en-US, not en_US).

Nuxt 3

Nuxt's @nuxtjs/i18n module generates hreflang tags automatically when seo: true is set in the module configuration. For manual control, use useHead composable.

// composables/useHreflang.ts
export function useHreflang(slug: string) {
  const baseUrl = 'https://example.com';
  const locales = ['en', 'de', 'fr'];

  useHead({
    link: [
      ...locales.map((locale) => ({
        rel: 'alternate' as const,
        hreflang: locale,
        href: `${baseUrl}/${locale}/${slug}/`,
      })),
      {
        rel: 'alternate' as const,
        hreflang: 'x-default',
        href: `${baseUrl}/en/${slug}/`,
      },
    ],
  });
}

Call useHreflang(slug) inside each page component that requires hreflang output.

Astro

Astro's getStaticPaths makes it straightforward to build the full set of variants for each page and pass them to a shared layout.

---
// src/layouts/BaseLayout.astro
interface Props {
  hreflangLinks: Array<{ hreflang: string; href: string }>;
}
const { hreflangLinks } = Astro.props;
---
<html>
  <head>
    {hreflangLinks.map((link) => (
      <link rel="alternate" hreflang={link.hreflang} href={link.href} />
    ))}
  </head>
  <body>
    <slot />
  </body>
</html>
---
// src/pages/[locale]/[slug].astro
export async function getStaticPaths() {
  const baseUrl = 'https://example.com';
  const locales = ['en', 'de', 'fr'];
  const slugs = ['about', 'pricing', 'contact'];

  return locales.flatMap((locale) =>
    slugs.map((slug) => ({
      params: { locale, slug },
      props: {
        hreflangLinks: [
          ...locales.map((l) => ({
            hreflang: l,
            href: `${baseUrl}/${l}/${slug}/`,
          })),
          { hreflang: 'x-default', href: `${baseUrl}/en/${slug}/` },
        ],
      },
    }))
  );
}
const { hreflangLinks } = Astro.props;
---
<BaseLayout hreflangLinks={hreflangLinks}>
  <!-- page content -->
</BaseLayout>

TanStack Start

TanStack Start uses file-based routing with createFileRoute. Inject hreflang tags using the head export from a route file.

// routes/$locale.product.$slug.tsx
import { createFileRoute } from '@tanstack/start';

const locales = ['en', 'de', 'fr'] as const;
const baseUrl = 'https://example.com';

export const Route = createFileRoute('/$locale/product/$slug')({
  head: ({ params }) => {
    const { locale, slug } = params;

    const hreflangLinks = [
      ...locales.map((l) => ({
        tag: 'link' as const,
        attrs: {
          rel: 'alternate',
          hreflang: l,
          href: `${baseUrl}/${l}/product/${slug}/`,
        },
      })),
      {
        tag: 'link' as const,
        attrs: {
          rel: 'alternate',
          hreflang: 'x-default',
          href: `${baseUrl}/en/product/${slug}/`,
        },
      },
    ];

    return { links: hreflangLinks };
  },
  component: ProductPage,
});

function ProductPage() {
  const { locale, slug } = Route.useParams();
  return <div>{/* page content */}</div>;
}

Frameworks with built-in i18n routing, like Next.js with @formatjs/intl or platforms like Better i18n that manage translation delivery, can generate or automate hreflang output at build or runtime — useful when the number of locales and routes scales beyond what is practical to maintain manually.


Common Mistakes

Missing Self-Reference

Every page must declare itself. Forgetting this is the number one reason Google's Search Console reports hreflang errors. The self-reference must use the exact same URL as the <loc> value in your sitemap, including trailing slash, protocol, and subdomain.

<!-- WRONG: /de/ page does not reference itself -->
<link rel="alternate" hreflang="en" href="https://example.com/en/about/" />
<link rel="alternate" hreflang="fr" href="https://example.com/fr/about/" />

<!-- CORRECT: /de/ page includes its own self-reference -->
<link rel="alternate" hreflang="de" href="https://example.com/de/about/" />
<link rel="alternate" hreflang="en" href="https://example.com/en/about/" />
<link rel="alternate" hreflang="fr" href="https://example.com/fr/about/" />

If you add a new language variant to your site but only update the templates for the new language, the existing pages will not point back to the new variant. Google sees a one-way declaration and ignores it.

When you add a new locale, you must update every existing page in the set to include a link to the new variant.

Wrong Language Codes

hreflang values are case-insensitive in the specification, but using the wrong codes produces silent failures. Common errors:

  • en_US instead of en-US (underscore vs hyphen)
  • zh when you mean either zh-Hans (Simplified) or zh-Hant (Traditional)
  • pt when you need to distinguish pt-PT (Portugal) from pt-BR (Brazil)
  • Using a three-letter ISO 639-2 code (zho, deu) instead of the two-letter ISO 639-1 code (zh, de)

Google's documentation specifies ISO 639-1 for language codes and ISO 3166-1 alpha-2 for country codes. There are a small number of exceptions where no two-letter ISO 639-1 code exists, but for the languages covered by virtually all international websites, the two-letter code is correct.

Mixing hreflang with Incorrect Canonical Tags

If your canonical tag on a page points to a different URL than the hreflang self-reference, Google may resolve the conflict by ignoring hreflang and using the canonical instead. The canonical and the hreflang self-reference must point to the same URL.

<!-- WRONG: canonical and hreflang self-reference disagree -->
<link rel="canonical" href="https://example.com/product/" />
<link rel="alternate" hreflang="en" href="https://example.com/en/product/" />

<!-- CORRECT: canonical and self-reference point to the same URL -->
<link rel="canonical" href="https://example.com/en/product/" />
<link rel="alternate" hreflang="en" href="https://example.com/en/product/" />

Using hreflang for Non-Equivalent Pages

hreflang declares that two pages are translations or regional variants of each other. Using it to connect pages that are not genuinely equivalent — for example, linking a blog post to a homepage because both are in English — causes Google to misinterpret your site structure.


Validation Tools

Google Search Console

The Coverage report in Google Search Console surfaces hreflang errors directly. Common error types you will see:

  • Alternate page with proper canonical tag: The alternate page has a canonical tag that points to itself (correct behaviour, not an error, despite the label).
  • Return tag missing: Google found an hreflang link but the target page does not link back. Check the specific URL reported and add the missing return tag.
  • No hreflang tag: A page is referenced as an alternate but does not itself contain hreflang tags.

Search Console's International Targeting report (under Legacy Tools) also lets you set a geographic target for ccTLD-less sites, which complements hreflang for regional targeting.

hreflang Checker Tools

Several third-party tools validate hreflang implementations by crawling a URL and checking the full set of return links:

  • hreflang.org Tag Checker: Paste a URL to see all hreflang annotations found on that page and whether return links exist on each target page.
  • Aleyda Solis's hreflang Tags Testing Tool: A free tool from one of the foremost international SEO practitioners, useful for spot-checking individual URLs.
  • Merkle's hreflang Tag Generator: Generates syntactically correct HTML, HTTP header, or sitemap markup from a structured input.

Screaming Frog SEO Spider

For large-scale audits, Screaming Frog's SEO Spider crawls your entire site and produces a report showing every hreflang annotation found, the target URLs, and whether the bidirectionality requirement is satisfied. The "hreflang" tab in the report flags missing return links, invalid language codes, and mismatched canonicals across the entire crawl. This is the most practical tool for validating an implementation before launch on sites with hundreds or thousands of pages.


FAQ

Does hreflang affect rankings directly?

No. hreflang tells Google which page to serve for a given audience, but it does not provide a ranking boost. It prevents duplicate content issues and ensures the right page competes for the right audience, which can improve click-through rates and engagement signals indirectly.

Can I use hreflang on a site with a single language but multiple country versions?

Yes. If you have distinct pages for English speakers in the US, UK, and Australia with genuinely different content (pricing, legal, product availability), using en-US, en-GB, and en-AU tags is correct. If the content is identical, maintaining separate URLs adds complexity with minimal benefit — consider geo-targeting via Google Search Console's International Targeting tool instead.

How long does it take for Google to process hreflang changes?

Google processes hreflang annotations during its normal crawl cycle. For most sites this means days to weeks before changes are fully reflected. Submitting an updated XML sitemap through Search Console accelerates the process by signalling to Googlebot that content has changed.

Should I use hreflang on every page, or only key pages?

For international SEO, hreflang should be implemented on every page that has a translated variant. Partial implementation causes inconsistencies: Google may process some URLs in the set and ignore others, leading to unpredictable behaviour in which variant is shown for which audience.

What happens if I have hreflang errors and do nothing?

Google treats erroneous or incomplete hreflang annotations as if they were not present and falls back to its own signals to determine which page to show. For sites targeting multiple countries with similar-language content, this typically means the wrong regional variant appears in search results — or that ranking signals are split across URLs instead of consolidated.


Conclusion

hreflang tags are one of the more technically demanding SEO requirements for international sites, not because the individual tag is complicated, but because the implementation must be consistent across every page, every variant, and every method you choose. A single broken return link or one missing self-reference can silently invalidate part of your international targeting.

The practical checklist for a sound implementation is short: use BCP 47 language codes with hyphens, always include a self-reference, always maintain bidirectional links, align your canonical tags with your hreflang self-references, and validate with Screaming Frog and Search Console before launch.

For new projects, the path of least resistance is to choose a framework or platform that generates hreflang output automatically as part of its i18n routing — and then validate the output rather than maintain it by hand. For existing sites, an XML sitemap implementation is usually the lowest-friction retrofit.


References