Skip to content
51studio
Tutorials

Hreflang done right

By Sam Hollis9 min read

Hreflang is treated as the exotic part of international SEO. It's actually a checklist of about six things, three of which trip up every team the first time they implement it.

This is the working setup. The bugs to know about. The cases where you don't actually need it.

What hreflang is for

Hreflang tells Google which version of a page to show to which user, when you have the same content in multiple languages or regions. It does two things:

  1. Avoids the duplicate-content penalty for the same article in different languages.
  2. Routes the right user to the right language version, so an English speaker doesn't land on the French page from a Google search.

If you have a single-language site, you don't need hreflang. If you have a multi-language site, you need it. The middle case — multi-region but single-language (US English vs UK English) — is where most of the bugs happen.

The three failure modes

1. The reciprocity rule

Hreflang must be reciprocal. If page A links to page B as the French version, page B must link to page A as the English version. If only one side declares the relationship, Google ignores both.

html
<!-- On example.com/en/about -->
<link rel="alternate" hreflang="en" href="https://example.com/en/about" />
<link rel="alternate" hreflang="fr" href="https://example.com/fr/about" />

<!-- On example.com/fr/about — MUST be present, same tags -->
<link rel="alternate" hreflang="en" href="https://example.com/en/about" />
<link rel="alternate" hreflang="fr" href="https://example.com/fr/about" />

The bug we see: teams generate the hreflang tags from a locales array but forget to include the current page in its own list. Each page lists "the other locales" instead of "all locales including this one." Reciprocity breaks, Google ignores the lot.

Rule of thumb: every page lists every version, including itself. The self-reference looks redundant but is required.

2. The x-default trap

x-default is the fallback. When Google can't pick a matching locale for the user (their language isn't in your list), it falls back to the x-default URL.

The bug: teams set x-default to the English version. Then a French user with their browser set to French gets routed to English instead of French, because Google's logic checks x-default before exhausting all language matches.

The right pattern: x-default should point to your "language picker" page if you have one, or to your most generic URL (e.g. the root / that auto-detects locale and redirects). Not to a specific language version.

html
<link rel="alternate" hreflang="x-default" href="https://example.com/" />
<link rel="alternate" hreflang="en" href="https://example.com/en/about" />
<link rel="alternate" hreflang="fr" href="https://example.com/fr/about" />

The exception: if your default is genuinely the English version (no language picker, English is the canonical site), then x-default and en can point to the same URL. Just make it explicit.

3. Region vs. language codes

Hreflang uses two-letter language codes (ISO 639-1) optionally followed by a two-letter region code (ISO 3166-1). The combinations matter more than people think.

  • en — English speakers anywhere
  • en-US — English speakers in the United States specifically
  • en-GB — English speakers in the UK specifically
  • en-us (wrong case) — Google should accept this, but the standard is uppercase region

If you have one English version, use en. If you have separate US and UK versions, use en-US and en-GB (and put both in every page's tag list, with reciprocity).

The bug: teams declare en-US and en-GB but no plain en. A user in Canada (English-speaking but neither US nor GB) gets routed somewhere unpredictable, because there's no language-only fallback. Either add en pointing to whichever version you want as the English fallback, or set x-default properly.

A correct setup, end to end

For a marketing site available in English and German:

html
<!-- On every page, both locales -->
<link rel="alternate" hreflang="en" href="https://example.com/en/services" />
<link rel="alternate" hreflang="de" href="https://example.com/de/services" />
<link rel="alternate" hreflang="x-default" href="https://example.com/" />

The root / either auto-detects and redirects (using Accept-Language header) or shows a language picker. Either is fine for x-default.

In Next.js with App Router, this typically lives in generateMetadata:

ts
export async function generateMetadata({ params }) {
  const { locale, slug } = await params;
  return {
    alternates: {
      canonical: `https://example.com/${locale}/services/${slug}`,
      languages: {
        en: `https://example.com/en/services/${slug}`,
        de: `https://example.com/de/services/${slug}`,
        "x-default": `https://example.com/`,
      },
    },
  };
}

Next.js renders the right <link rel="alternate"> tags in the head. All you have to do is supply the URL pattern.

A canonical URL note

Hreflang and canonical URLs need to agree.

  • Each page's canonical URL is itself (the locale-specific URL).
  • Each page's hreflang="x" for its own locale points to the same URL as the canonical.

The bug: a page has canonical /en/services but its hreflang="en" points to /services (no locale prefix). Google sees the conflict and may ignore both directives. The two must match.

How to test

Three tools that catch most of what manual review misses:

Google Search Console. The "International Targeting" report lists hreflang errors detected during crawl. Missing reciprocity, conflicting alternates, invalid codes. Check this report weekly during the first month after launch.

`hreflang.org`'s checker (or similar). Paste any URL and it crawls the hreflang tags, follows them, and reports mismatches. Useful for the first sanity check.

The `<link>` tags in dev tools. Open any page, inspect the <head>, search for alternate. Confirm reciprocity manually for a handful of pages.

Run all three before considering the implementation done. None of them is sufficient alone.

When you actually need it

Hreflang is required when:

  • You have the same content in two or more languages (e.g. blog post in EN and UK).
  • You have the same content for two or more regions of the same language (US vs UK English).

Hreflang is not needed when:

  • You have a single-language site (regardless of country).
  • You have a multi-language site where each language is genuinely different content (not translations of each other).
  • You have a language picker but all language URLs are at the same path (which is broken for SEO anyway; fix the URLs first).

The middle case — "I have an English site and want it to rank in different countries" — is solved by Search Console's geo-targeting setting, not hreflang. Don't confuse them.

Edge cases worth knowing

Currency or content variants. Same language, different prices for US/UK/EU. Use en-US, en-GB, en-EU. The content can be 95% the same; the variant for SEO is the price/currency that's localised.

Subdomains vs. subdirectories. Hreflang works the same way for both. en.example.com and fr.example.com work as well as example.com/en/ and example.com/fr/. Use whichever your CMS supports cleanly.

Mid-rollout (some pages translated, some not). Don't declare hreflang for pages that don't exist in the target language. A 404 with hreflang is worse than no hreflang at all.

Auto-translated content. Don't put hreflang on it. Google now penalises auto-translated content shown as "native." Either use real translations or skip the language entirely.

A checklist before shipping

  • Every page lists every locale, including itself.
  • x-default points to the language picker or generic root, not a specific language.
  • Language codes are correct (en, not en_US underscore; en-GB, not en_GB).
  • Each page's canonical URL matches its own hreflang entry.
  • Search Console International Targeting report has no errors.
  • Pages that don't exist in a target locale aren't declared in that locale's hreflang.

Five items checked properly, you've covered the cases that actually matter. The remaining edge cases (mid-rollout, auto-translation, region-only variants) are addressable as they come up.

If you're building a multi-language site and want hreflang baked in from the start, see how we work on websites. It's part of every project we ship, not an upsell.

Related articles