hreflang x-default for Headless Locale Selectors
The x-default annotation answers a single question — where should someone go when none of your locales match them — and in a decoupled build it is usually answered by an IP-based redirect that Googlebot never experiences.
When to Use This Approach
Set or fix x-default when:
- The site serves three or more locales and traffic arrives from countries none of them target.
- Search Console’s international targeting report shows alternate pages without return tags.
- A geolocation redirect sits in front of the locale URLs, which is the usual root cause.
What the Fallback Is Actually For
Every alternate says “this URL is for these speakers in this country.” The default says “and everyone else goes here.” Without it, everyone else is assigned by inference.
Implementation Steps
Step 1: Decide what the fallback should be
Two viable choices:
a) The most broadly applicable locale (usually international English at /)
-> best when one locale genuinely serves an unmatched visitor well
b) A language selector page with real content
-> only when you have distinct regional offerings and the selector
page itself is useful, not an empty list of flags
Validation: Ask what a visitor from an unlisted country actually needs. If the answer is “the English version”, option (a) is correct and simpler.
Step 2: Make the default target directly crawlable
This is where most implementations fail: the x-default URL exists but redirects by IP, so a crawler never sees it.
// middleware.js — never redirect a crawler or the default URL by geolocation
const LOCALE_PREFIXES = new Set(['de', 'fr', 'uk', 'jp']);
export function middleware(request) {
const { pathname } = new URL(request.url);
const first = pathname.split('/')[1];
// A request that already names a locale is served as-is.
if (LOCALE_PREFIXES.has(first)) return NextResponse.next();
// The default path is served as-is too — no geolocation redirect.
return NextResponse.next();
}
SEO impact: Every alternate, including the default, returns 200 to any requester, which is the precondition for the whole annotation set being accepted.
Validation: curl -s -o /dev/null -w '%{http_code} %{redirect_url}\n' https://example.com/products/ returns 200 with no redirect target.
Step 3: Generate the full alternate set, including the default
Every page in a locale group must list every alternate and the default. A partial set is treated as no set.
// lib/alternates.js — one source of truth for the whole group
export const LOCALES = [
{ code: 'en-GB', prefix: '/uk' },
{ code: 'de-DE', prefix: '/de' },
{ code: 'fr-FR', prefix: '/fr' },
{ code: 'ja-JP', prefix: '/jp' },
];
export const DEFAULT_PREFIX = ''; // the root, international English
export function alternatesFor(entry) {
const languages = Object.fromEntries(
LOCALES.map((l) => [l.code, `https://example.com${l.prefix}/${entry.paths[l.code]}/`]),
);
languages['x-default'] = `https://example.com${DEFAULT_PREFIX}/${entry.paths.default}/`;
return languages;
}
// app/[[...locale]]/products/[slug]/page.jsx
export async function generateMetadata({ params }) {
const entry = await cms.products.getGroup(params.slug);
return { alternates: { languages: alternatesFor(entry) } };
}
SEO impact: Every URL in the group declares the complete set, which is what makes the return-tag requirement satisfiable.
Validation: curl -s <any locale url> | grep -c 'hreflang=' returns the locale count plus one, on every alternate.
Step 4: Replace geolocation redirects with a banner
A suggestion preserves the visitor’s choice and the crawler’s access at once.
// components/LocaleSuggestion.jsx — suggest, never redirect
export function LocaleSuggestion({ currentLocale, suggested }) {
if (!suggested || suggested === currentLocale) return null;
return (
<aside role="note" className="locale-suggestion">
<p>This page is also available in {suggested.label}.</p>
<a href={suggested.href} hrefLang={suggested.code}>
Switch to {suggested.label}
</a>
</aside>
);
}
SEO impact: Googlebot crawling from any location reaches the URL it requested, so every alternate is fetched and every annotation can be confirmed.
Validation: Request each locale URL with a geographically varied proxy and confirm all return 200 without a Location header.
Step 5: Verify the annotations resolve both ways
#!/usr/bin/env bash
# ci/hreflang-check.sh — every alternate must return 200 and name the group
set -euo pipefail
url="$1"
alts=$(curl -s "$url" | grep -oP '(?<=hreflang=")[^"]+" href="[^"]+' \
| sed 's/" href="/\t/')
echo "$alts" | while IFS=$'\t' read -r lang href; do
code=$(curl -s -o /dev/null -w '%{http_code}' "$href")
back=$(curl -s "$href" | grep -c "href=\"$url\"")
printf '%-10s %s %s return-tag=%s\n' "$lang" "$code" "$href" "$back"
done
Validation: Every row shows 200 and a non-zero return-tag count. Any row with a 3xx is a geolocation redirect still in place.
Why Redirects Break the Whole Set
The failure is not that one annotation is wrong; it is that the crawler never reaches the pages that would confirm the others.
SEO Impact Summary
The unmatched population is larger than most teams expect, which is why the fallback choice is worth making deliberately rather than letting Google infer it.
| Signal | What improves | What breaks if misconfigured |
|---|---|---|
| Locale targeting | Unmatched visitors land on a deliberate fallback rather than an inferred one | An x-default pointing at a redirecting URL is ignored entirely |
| Alternate recognition | Every locale is fetched, so return tags confirm and the group is accepted | Geolocation redirects leave most alternates uncrawled |
| Duplicate control | Locale variants are recognised as alternates rather than duplicates | An unrecognised group means locales compete with each other |
| Crawl efficiency | No redirect hops between locale URLs | Every locale request costing a redirect wastes budget at locale scale |
Measurable signals to watch:
- Count of alternates returning
200to an out-of-region request, which should be all of them. - International targeting errors in Search Console, specifically missing return tags.
- Impressions per locale, where a single dominant locale despite multi-market content suggests the group is not recognised.
Edge Cases and Gotchas
x-default must appear in every page’s set, not only on the home page A group is a per-page relationship. Declaring the default only at the root leaves every deeper page without a fallback.
Language-only codes are usually the right level
de rather than de-DE unless you genuinely serve different content to Germany and Austria. Over-specifying creates groups you then have to maintain per country.
The default target must be in the group
It is an additional annotation on an existing alternate, not a separate page outside the set. If /products/ is the default it must also be listed as a normal alternate with its own language code.
Sitemap annotations are an alternative, not an addition You can declare alternates in the sitemap instead of the HTML, but declaring both invites disagreement. Pick one channel — the reasoning parallels canonical tags vs HTTP Link headers.
Frequently Asked Questions
Is x-default required? Optional, but omitting it means a visitor whose language and country match no alternate gets no guidance and Google chooses arbitrarily. On a site with more than a few locales that arbitrary choice is wrong often enough to justify the annotation.
Should x-default point at a language selector or a real page? A real page, unless the selector genuinely has content. An empty chooser is a poor landing experience and a thin page. Most sites are better served pointing it at their most broadly applicable locale.
Why do geolocation redirects break hreflang? Googlebot crawls predominantly from a small number of locations, so an IP redirect sends it to the same locale regardless of which alternate it requested. The others are never fetched, their annotations never confirmed, and the group is not recognised.
Does x-default affect which page ranks in a matched market? No. Where a locale matches, its own alternate is used. The default only governs the unmatched case, so adding it cannot displace a correctly targeted locale.
Part of: hreflang & International Routing
Related
- Validating hreflang Return Tags at Scale — automating the reciprocity check across thousands of groups
- Implementing hreflang in Headless Multi-Locale Sites — generating the alternate set from one locale source
- Handling Multi-Locale Slug Transliteration — keeping the per-locale paths in the group stable