Hreflang and international URL structure, a practical guide
Hreflang is for sites with equivalent pages in more than one language or for more than one region, and it matters most when versions share a language, such as US and UK English. It tells Google which URL to show to which searcher, so it won't lift rankings or rescue pages that aren't true equivalents, and it only works when every page in a set confirms every other page.
Two decisions come before any markup: whether you need separate versions at all, and which URL structure they live on. Getting either wrong creates work that hreflang can't undo.
Decide whether you need separate versions
| Your situation | Separate URLs? | Hreflang? |
|---|---|---|
| One language, one offer for everyone | No | No |
| Same language, different prices, delivery, spelling or legal terms (en-US, en-GB, en-AU) | Yes | Yes, this is where it helps most |
| Full translations of the same pages | Yes | Recommended |
| Main content in one language, only navigation and footer translated | Usually already the case | Recommended by Google |
| Different content per language, not translations | Yes | No, the pages aren't alternates |
| Some pages translated, others not | For the translated pages | Only between pages that have an equivalent |
Three details shape that table. Google's guide to localized versions says it doesn't use hreflang or the HTML lang attribute to detect a page's language; it works the language out from the content. Its international site guidance adds that it uses visible content, so each page should use one language for content and navigation, without side-by-side translations. And hreflang connects equivalents: a German article that covers a different topic from its English neighbor gets no annotation linking the two.
Partial translation is common and manageable. Google says that if a complete set of links is hard to maintain, you can leave some languages out on some pages, and it will still process the pairs that point to each other.
Redirecting visitors by location can undermine all of this. Google's international guidance says to avoid automatically redirecting users between language versions, and notes that most Google crawls originate from the US without varying location, so a crawler redirected by IP may never reach the other versions. A banner suggesting the local site, plus a visible language selector, gives visitors the right version without hiding anything from crawlers.
Choose the URL structure
Google's international guidance lays out the trade-offs of each structure. The right-hand column is a judgment based on those trade-offs, not Google's wording.
| Structure | Example | Google's pros | Google's cons | Usually fits |
|---|---|---|---|---|
| Country domain | example.de | Clear geotargeting, easy separation | Expensive, more infrastructure, one country per domain | Brands that need a strong local presence and can run separate sites |
| Subdomain | de.example.com | Easy setup, different server locations possible | Users can't tell if "de" means language or country | Markets run by separate teams or stacks |
| Subfolder | example.com/de/ | Easy setup, low maintenance on one host | Single server location, harder to separate | Most sites with one codebase |
| URL parameter | example.com?loc=de | None listed | Not recommended by Google | Avoid |
Two background facts matter here. Google treats some vanity country domains, such as .tv and .me, as generic. And Search Console no longer has a country targeting setting: Google deprecated the International Targeting report in 2022 while confirming it still supports hreflang. On a generic domain, then, hreflang and the content itself (currency, delivery details, local contact information) carry the regional signal.
Changing structure later means migrating every international URL, so choose with a few years in mind. If you do have to move, treat it like any other move, using the site migration checklist.
How a hreflang set works
The rules Google documents for a set, plus one practical addition at the end:
Every version lists itself and every other version. A page that lists only its siblings is incomplete.
Links must be returned. If page A names page B and B doesn't name A, Google ignores the annotation. That rule exists so another site can't declare itself an alternate of your pages.
URLs must be fully qualified, including https. A path like /de/page is not enough.
The language comes first as an ISO 639-1 code, optionally followed by an ISO 3166-1 Alpha 2 region code. A script can be added in ISO 15924 format, such as zh-Hant.
Alternates can sit on different domains.
x-default is optional. Google describes it as the fallback for languages you haven't listed and suggests it especially for language selectors and home pages that redirect automatically.
If you have several regional versions in one language, Google suggests a generic language tag as a catch-all for that language's other speakers. It can point at one of the regional pages.
Every URL in the set should be the canonical version, return 200 and be indexable.
Markup in the head
Here is a hypothetical set for a running shoe category in US English, UK English, Spain Spanish and Mexico Spanish. Every page in the set carries the identical block, plus its own self-referencing canonical:
<link rel="alternate" hreflang="en-US" href="https://www.example.com/en-us/running-shoes/">
<link rel="alternate" hreflang="en-GB" href="https://www.example.com/en-gb/running-shoes/">
<link rel="alternate" hreflang="en" href="https://www.example.com/en-us/running-shoes/">
<link rel="alternate" hreflang="es-ES" href="https://www.example.com/es-es/zapatillas-running/">
<link rel="alternate" hreflang="es-MX" href="https://www.example.com/es-mx/tenis-para-correr/">
<link rel="alternate" hreflang="es" href="https://www.example.com/es-mx/tenis-para-correr/">
<link rel="alternate" hreflang="x-default" href="https://www.example.com/en-us/running-shoes/">The plain en and es tags catch English speakers outside the US and UK and Spanish speakers outside Spain and Mexico. Sending them to the US and Mexico pages is a choice for this example; pick whichever version serves those visitors best.
The same set in an XML sitemap
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"
xmlns:xhtml="http://www.w3.org/1999/xhtml">
<url>
<loc>https://www.example.com/en-gb/running-shoes/</loc>
<xhtml:link rel="alternate" hreflang="en-US" href="https://www.example.com/en-us/running-shoes/"/>
<xhtml:link rel="alternate" hreflang="en-GB" href="https://www.example.com/en-gb/running-shoes/"/>
<xhtml:link rel="alternate" hreflang="en" href="https://www.example.com/en-us/running-shoes/"/>
<xhtml:link rel="alternate" hreflang="es-ES" href="https://www.example.com/es-es/zapatillas-running/"/>
<xhtml:link rel="alternate" hreflang="es-MX" href="https://www.example.com/es-mx/tenis-para-correr/"/>
<xhtml:link rel="alternate" hreflang="es" href="https://www.example.com/es-mx/tenis-para-correr/"/>
<xhtml:link rel="alternate" hreflang="x-default" href="https://www.example.com/en-us/running-shoes/"/>
</url>
<!-- repeat a <url> entry for each version, each with the same child list -->
</urlset>For PDFs and other files without a head, the same information goes in an HTTP Link header, listing every version on one line:
Link: <https://www.example.com/en-us/size-chart.pdf>; rel="alternate"; hreflang="en-US", <https://www.example.com/en-gb/size-chart.pdf>; rel="alternate"; hreflang="en-GB"Codes that look right and aren't
| Written | Problem | Use instead |
|---|---|---|
| en-UK | UK is a reserved code, not the United Kingdom's ISO code, so Google ignores the region part | en-GB |
| en-EU | EU isn't a country code, so Google ignores that part | en, or one tag per country |
| es-LA | LA is the code for Laos, so this targets Spanish speakers in Laos | One tag per country (es-AR, es-CO), or es |
| es-419 | 419 is a UN M.49 region number, not an ISO 3166-1 Alpha 2 code | One tag per country, or es |
| mx-es | Reversed, the language comes first | es-MX |
| us | A country on its own isn't valid, and Google won't derive a language from it | en-US |
| jp | A country code, not a language code | ja or ja-JP |
| se | The language code for Northern Sami, not Swedish | sv or sv-SE |
| dk | A country code; Danish is da | da or da-DK |
Latin America is a common trap, because neither LA nor 419 works. You either list each country you serve or use a language-only tag.
Canonicals and hreflang together
Same-language regional pages can be grouped as duplicates, since Google's canonicalization overview counts language versions as duplicates when the primary content is in the same language. Google's international guidance answers that with both tools together: canonical tags plus hreflang, so the correct regional URL is the one shown. In its guide to specifying a canonical URL, Google asks for a canonical in the same language when you use hreflang, and says it prefers URLs that are part of hreflang clusters when choosing canonicals.
In markup, the difference between right and wrong is one line:
<!-- On the en-GB page: correct -->
<link rel="canonical" href="https://www.example.com/en-gb/running-shoes/">
<link rel="alternate" hreflang="en-GB" href="https://www.example.com/en-gb/running-shoes/">
<link rel="alternate" hreflang="en-US" href="https://www.example.com/en-us/running-shoes/">
<!-- On the en-GB page: wrong -->
<link rel="canonical" href="https://www.example.com/en-us/running-shoes/">
<link rel="alternate" hreflang="en-GB" href="https://www.example.com/en-gb/running-shoes/">
<link rel="alternate" hreflang="en-US" href="https://www.example.com/en-us/running-shoes/">The wrong version tells Google the UK page is a copy of the US page, while the hreflang on the same page presents it as the UK alternate. Make the regional differences visible too: prices in the local currency, local delivery and returns, local spelling. Pages that differ only in a flag icon give Google little reason to treat them as separate. The wider rules for picking canonicals are in the post on how Google treats canonical tags.
Hreflang targets should also be the HTTPS canonical URLs. Google's canonical guide says not to include HTTP versions in hreflang annotations, and pointing a set at URLs that redirect or carry noindex leaves Google to work out which pages you meant.
Head tags, sitemaps or headers
Google's localized versions guide says the three methods are equivalent, and that using all of them at once brings no benefit in Search while making maintenance harder. Choose by page type and by how your site is built:
Head tags are the easiest to inspect, but every page carries one line per version. With 30 locales, that is 31 lines in every head once x-default is included. The tags must sit inside a well-formed head, and if a JavaScript framework injects them, check the rendered HTML, as the guide to JavaScript SEO explains.
Sitemaps keep the markup out of pages and centralize the logic, and Google says the child xhtml:link entries don't count toward the sitemap URL limit. Errors stay hidden longer, because nobody looks at sitemaps by accident. The post on XML sitemaps that help covers keeping them accurate.
HTTP headers are the only option for PDFs and other non-HTML files.
Whatever you choose, don't let two methods disagree for the same page. Two sources that contradict each other are harder to debug than either one alone.
A worked example, a US store adds a UK store
Say a US retailer sells at www.example.com with no locale in its URLs, and it starts shipping to the UK with prices in pounds. Moving the US pages under /en-us/ would turn a launch into a migration, so the US pages stay where they are and the UK store goes under /en-gb/.
| Element | Before | After |
|---|---|---|
| US product URL | /products/trail-shoe | Unchanged |
| UK product URL | None | /en-gb/products/trail-shoe |
| Canonicals | US page to itself | Each page to itself |
| Hreflang | None | en-US and en to the US page, en-GB to the UK page, x-default to the US page |
| Sitemaps | US URLs | US and UK URLs, each entry listing both versions if hreflang lives there |
| Prices and delivery | USD, US shipping | GBP with VAT, UK delivery and returns on UK pages |
| Internal links | US pages | UK pages link to UK pages; a selector links to the equivalent page, not the home page |
| UK visitors on US pages | Nothing | A banner offering the UK store, no forced redirect |
Pages that exist only for the US, such as a US-only promotion, get no en-GB annotation. They simply stay out of the set.
Keeping hreflang correct after launch
Hreflang drifts. Templates change in one locale and not another, a product is withdrawn in Germany while the French page still lists it, and two CMS instances generate their sets independently. With the International Targeting report gone, Search Console won't flag any of it, so validation has to come from your own crawls and checks.
Generate every set from one source of truth: a table that maps each page to its URL in each locale. Templates and sitemaps read from it, so removing a page in one market updates every set at once.
Crawl with a tool that checks return links, code validity and the status of every target, and run it after each release that touches templates or locales.
Add automated checks for the basics: every target returns 200, carries a self-referencing canonical, is indexable, and lists the same set as its siblings.
Check server logs to see whether Googlebot requests every locale, or keeps crawling one version and neglecting the rest. The log file analysis primer shows how to split crawl activity by folder.
To start, take a handful of URLs from your most important template in each locale and check three things for each one: the set lists the page itself, every target returns 200 with a self-referencing canonical, and every target links back.