Fastest fix: add one line per page — <link rel="alternate" hreflang="x-default" href="..."> pointing to the English version of that exact page (not your homepage, not a redirect). That clears the “missing x-default” flag and stops Google from guessing which language to show users outside your declared locales.
You declared hreflang on every page: en points to the English version, zh points to the Chinese version, both reference each other. Then a hreflang validator (Merkle, TechnicalSEO.com, Ahrefs Site Audit, or Screaming Frog) flags missing x-default. Worse, users from India, Brazil, or Germany Google your brand, land on the Chinese page, bounce, and your bounce rate climbs.
x-default is the hreflang value that controls what users outside your declared locales see. It is not optional once your audience extends beyond the languages you have listed. This article covers what it actually does, the six configurations that break it, and copy-ready fixes.
One thing changed since this used to be easy to check: Google removed the International Targeting report from Search Console in September 2022, so there is no longer a built-in site-wide hreflang report. hreflang itself is fully supported — only the report is gone. Validate with the URL Inspection tool (per-URL) plus a third-party crawler (site-wide). The verification steps below reflect this.
What x-default actually means
From Google’s documentation: the reserved x-default value “is used when no other language/region matches the user’s browser setting.” It is not “the crawler’s fallback.” It is not “the homepage.” It is the page-level default for that specific piece of content, and Google also names it as the right value for language-selector or gateway pages that route users by browser language or IP.
Example: a user in Germany browses to yourdomain.com/article with Accept-Language: de. You have en and zh declared but no de. Without x-default, Google picks one version on its own — usually weighted by signals like backlinks, which often means it guesses wrong. With x-default set to the English page, Google reliably serves English to that German visitor.
How to identify which case you’re in
Case 1: Missing x-default entirely
How to spot it:
curl -s https://yourdomain.com/en/article/ | grep hreflang
# Returns:
# <link rel="alternate" hreflang="en" href="https://yourdomain.com/en/article/">
# <link rel="alternate" hreflang="zh" href="https://yourdomain.com/zh/article/">
# (no x-default line)
A hreflang validator (Merkle, TechnicalSEO.com, Screaming Frog) shows a “missing x-default” / “no x-default” warning. The site still works for en and zh users, but third-locale users get arbitrary routing.
Why it happens: most CMS hreflang plugins generate one link per declared locale and never emit x-default because no one configured it. Hand-written templates often forget it.
Fix: add x-default pointing to your default version (usually English):
<link rel="alternate" hreflang="en" href="https://yourdomain.com/en/article/">
<link rel="alternate" hreflang="zh" href="https://yourdomain.com/zh/article/">
<link rel="alternate" hreflang="x-default" href="https://yourdomain.com/en/article/">
x-default can (and usually should) point to the same URL as one of your declared locales. The English-page URL appears twice — once as hreflang="en" and once as hreflang="x-default". That is correct.
Case 2: x-default points to the homepage instead of the equivalent page
How to spot it: on a deep article URL, view source and check the hreflang block:
<!-- on /en/article/some-post -->
<link rel="alternate" hreflang="en" href="https://yourdomain.com/en/article/some-post/">
<link rel="alternate" hreflang="zh" href="https://yourdomain.com/zh/article/some-post/">
<link rel="alternate" hreflang="x-default" href="https://yourdomain.com/"> <!-- BAD -->
Why it happens: someone misread the spec as “x-default is the site’s default page.” It is not — it is the equivalent default version of the current page.
The damage: a German user clicks a search result for “your-article topic,” lands on your homepage instead of the article they searched for, and bounces. You’re losing every long-tail visitor outside your declared locales.
Fix: x-default must point to the page-level default. On /en/article/some-post/, x-default points to /en/article/some-post/, not /.
Case 3: x-default points to a redirect
How to spot it:
curl -sI "https://yourdomain.com/article" | head -1
# HTTP/2 301
Your x-default is https://yourdomain.com/article but that URL 301s to https://yourdomain.com/en/article/. Google does follow the redirect, but the signal is weakened and some validators flag it.
Why it happens: legacy URL structure where /article used to be the English page before you introduced /en/ and /zh/ subdirectories. The redirect was set up but x-default was never updated.
Fix: point x-default to the final destination URL, not a redirect.
Case 4: Different pages in the same site declare x-default inconsistently
How to spot it: crawl your sitemap and grep all x-default declarations.
node -e "
const xml = await (await fetch('https://yourdomain.com/sitemap.xml')).text();
const urls = [...xml.matchAll(/<loc>(.*?)<\/loc>/g)].map(m => m[1]);
for (const u of urls) {
const html = await (await fetch(u)).text();
const m = html.match(/hreflang=[\"']x-default[\"']\s+href=[\"']([^\"']+)[\"']/);
console.log(u, '->', m ? m[1] : '(missing)');
}
" > x-default-audit.tsv
Then sort and look for outliers. If 90% of pages have x-default pointing to /en/ equivalents but a handful point to /zh/ or to the homepage, those are mistakes.
Why it happens: a CMS plugin update changed defaults; a few pages were hand-edited.
Fix: regenerate hreflang for the whole site from one source of truth (see “Shortest fix path” below).
Case 5: x-default in sitemap conflicts with x-default in head
How to spot it: the page HTML says x-default = /en/article/, but sitemap.xml declares x-default = /zh/article/ via <xhtml:link>. Google logs a warning and picks one inconsistently.
Why it happens: two different systems emit hreflang (the CMS for the page head, a separate sitemap generator), and they disagree.
Fix: emit hreflang from one source, not two. Either head tags or sitemap entries — pick one channel.
Case 6: x-default looks present but the locale codes are invalid
How to spot it: the x-default line is there, the page validates “structurally,” yet the per-locale tags do nothing. Run the page through Merkle’s hreflang testing tool or Screaming Frog and look for “incorrect language/region codes.”
<!-- BAD: en-UK is not a valid region code -->
<link rel="alternate" hreflang="en-UK" href="https://yourdomain.com/en/article/">
<link rel="alternate" hreflang="zh_CN" href="https://yourdomain.com/zh/article/">
Why it happens: hreflang uses ISO 639-1 language codes and ISO 3166-1 alpha-2 country codes, joined by a hyphen. Two traps catch almost everyone:
- The UK’s country code is
GB, notUK.en-UKis invalid; useen-GB. - The separator is a hyphen, not an underscore.
zh_CNis invalid; usezh-CN(or justzhif you do not split by region).
Google silently ignores invalid codes — the tag renders, but it carries no signal, so the locale (and its share of the x-default logic) effectively disappears. This is why a page can “have hreflang” and still route badly.
Fix: lowercase the language, uppercase the region, hyphen between them: en, zh, en-GB, zh-CN, pt-BR. Keep x-default exactly as x-default (the only non-locale value allowed).
Diagnosis table
Match the symptom to the case before you start editing.
| Symptom | Likely case | First check |
|---|---|---|
| Validator says “missing x-default” | Case 1 | curl ... | grep x-default returns nothing |
| Third-locale users land on the homepage | Case 2 | x-default href ends in / not the article path |
| Validator flags a redirect on the x-default URL | Case 3 | curl -sI <x-default-url> returns 301/302 |
| Some pages route fine, a few route wrong | Case 4 | Site-wide x-default audit shows outliers |
| Page head and sitemap disagree | Case 5 | Compare <head> tag vs sitemap.xml <xhtml:link> |
| hreflang “present” but a locale is ignored | Case 6 | Validator flags “incorrect language/region code” |
Shortest fix path
In hit-rate order:
- Add the
x-defaultline if missing → clears the validator warning and routes third-locale users to your English page on the next crawl. - Verify
x-defaultpoints to the page-level equivalent, not the homepage → fixes the silent UX bleed. - Confirm every locale code is valid (
en,zh,en-GB, noten-UK/zh_CN) → stops Google from silently dropping a locale. - Regenerate all hreflang tags from a single helper → prevents drift across templates.
- Re-validate with a crawler and the URL Inspection tool → confirms Google reads consistent, reciprocal hreflang now.
A single source of truth for hreflang
Build all hreflang and x-default from one function so every page is structurally identical:
// src/lib/hreflang.js
const BASE = "https://yourdomain.com";
const DEFAULT_LANG = "en";
export function buildHreflang(currentLang, slug, availableLangs) {
const tags = availableLangs.map((lang) => ({
hreflang: lang,
href: `${BASE}/${lang}/${slug}/`,
}));
tags.push({
hreflang: "x-default",
href: `${BASE}/${DEFAULT_LANG}/${slug}/`,
});
return tags;
}
In your layout:
---
import { buildHreflang } from "../lib/hreflang.js";
const tags = buildHreflang(lang, slug, ["en", "zh"]);
---
{tags.map((t) => (
<link rel="alternate" hreflang={t.hreflang} href={t.href} />
))}
Every page renders the same shape: one entry per locale, plus x-default pointing to the English equivalent of the current page. No hand-edits.
A build-time check
Add a prebuild check that fails the build if any HTML output is missing x-default:
// scripts/check-x-default.mjs
import fg from "fast-glob";
import fs from "node:fs";
const files = fg.sync("dist/**/*.html");
const missing = [];
for (const f of files) {
const html = fs.readFileSync(f, "utf8");
const hasHreflang = /hreflang=["'](en|zh)["']/.test(html);
const hasXDefault = /hreflang=["']x-default["']/.test(html);
if (hasHreflang && !hasXDefault) missing.push(f);
}
if (missing.length) {
console.error("Missing x-default:\n" + missing.join("\n"));
process.exit(1);
}
How to confirm it’s fixed
- Per URL: in Search Console, open URL Inspection, paste the live URL, and view the crawled HTML — confirm the
x-defaultline is present and points to the correct page-level English URL. - One page, fast: paste the URL into Merkle’s hreflang testing tool. It reads HTML tags, HTTP headers, and sitemap entries, and flags missing return links, bad codes, and missing
x-default. - Whole site: crawl with Screaming Frog (or Ahrefs Site Audit) and filter the hreflang issues. You want zero “missing x-default,” zero “non-reciprocal,” and zero “incorrect language/region code.”
- Reciprocity: for each pair, confirm both pages list each other. Google’s rule is blunt: “If two pages don’t both point to each other, the tags will be ignored.”
Prevention
- Always emit hreflang from one helper — no per-template hand-writing.
x-defaultpoints to the page-level equivalent, not the site homepage.- The
x-defaultURL must return 200 directly — never a redirect chain. - Use valid codes only: ISO 639-1 language lowercase, ISO 3166-1 alpha-2 region uppercase (
en,en-GB), hyphen-separated. - Re-validate with a crawler and URL Inspection after every template change (the old GSC International Targeting report is gone).
- If you add a third locale, update the helper, not 1000 templates.
FAQ
Q: Is x-default required?
A: Not strictly — Google will work without it. But for any site with users outside your declared locales (most public-facing sites), it is strongly recommended. Without it, Google guesses, and the guess is often wrong.
Q: Can x-default point to the same URL as an existing language?
A: Yes — that is the standard pattern. Typically x-default and hreflang="en" both point to the English page. Same URL, two <link> entries, both valid.
Q: Does x-default redirect users automatically?
A: No. x-default only tells Google which version to surface in search results for users without a locale match. It does not perform a redirect. If you want browser-level locale negotiation, implement Accept-Language-based routing separately.
Q: My hreflang is in the sitemap, not the page head. Do I still need x-default?
A: Yes. The xhtml:link tags in sitemap.xml follow the same rules — include an x-default entry alongside the per-locale entries. Just don’t emit hreflang from both the head and the sitemap (see Case 5).
Q: How do I validate hreflang now that Search Console removed the International Targeting report? A: Google retired that report in September 2022, but still fully supports hreflang. Validate per URL with the URL Inspection tool (look at the crawled HTML), and site-wide with a third-party crawler — Merkle/TechnicalSEO.com, Ahrefs Site Audit, or Screaming Frog. They surface the same errors the old report did, plus reciprocity and bad-code checks.
Q: A validator flags “incorrect language/region code.” Is that an x-default problem?
A: Usually a separate but related bug. The UK’s country code is GB (en-UK is invalid), and codes use hyphens (zh-CN, not zh_CN). Google ignores invalid codes silently, so the locale carries no signal even though the tag renders. Fix the code, then re-validate (see Case 6).
Q: A tool says my “hreflang tag is invalid.” Is x-default the cause?
A: Possibly. The most common causes: (1) x-default missing, (2) x-default pointing to a 4xx or redirecting URL, (3) the target doesn’t reciprocate — the page x-default points to must include this page in its own hreflang block, or Google ignores both. See hreflang warning in Search Console for the full diagnostic flow.
Related articles
- Hreflang warning in Search Console
- Canonical mismatch in bilingual sites
- Canonical misconfigured: 3 failure modes
- Why your new website is not showing up on Google
- Alternate page with proper canonical tag
Tags: #SEO #Troubleshooting #Debug #Structured data #hreflang #x-default #Bilingual