Adding Missing Self-Referencing Hreflang
Every page in a hreflang cluster has to list itself as well as the other versions. When the German page names the English and French pages but leaves out its own German line, the Hreflang Checker reports a missing self-reference.
It is one of the most common hreflang errors, because the code that builds the annotations is often borrowed from a language switcher, and a switcher naturally hides the language you are already reading. This guide explains why the self line matters, the other ways a self-reference can go missing even when it looks present, and how to fix the template once for every page.
Why a page has to name itself
Google's documentation for hreflang says each language version must list itself as well as all other versions. The reason is practical. The set of hreflang lines on a page describes the whole cluster that page belongs to, and Google compares the sets it finds on different pages to work out which pages form a group and in what roles. A page that leaves itself out is describing a cluster it is not part of.
Think of it as a register that every member signs. The German page's copy of the register should read: English is here, French is here, and German, that is me, is here. Without the last entry, the German page has vouched for its neighbours but not declared which language it serves in the group.
In practice, a missing self line rarely breaks everything at once. Google may still work out the relationship from the other pages. But it makes the cluster less reliable, it is the error that search engine documentation and most audit tools treat as a basic requirement, and it is usually a sign that the annotation code was written the wrong way round.
What a missing self line looks like
This is the head of the German page with the self-reference missing. It lists every version except the one you are on:
<!-- On https://example.com/de/kontakt/ : incomplete -->
<link rel="alternate" hreflang="en" href="https://example.com/contact/">
<link rel="alternate" hreflang="fr" href="https://example.com/fr/contact/">
<link rel="alternate" hreflang="x-default" href="https://example.com/contact/">
And the corrected version, where the only change is the added German line:
<!-- On https://example.com/de/kontakt/ : complete -->
<link rel="alternate" hreflang="en" href="https://example.com/contact/">
<link rel="alternate" hreflang="de" href="https://example.com/de/kontakt/">
<link rel="alternate" hreflang="fr" href="https://example.com/fr/contact/">
<link rel="alternate" hreflang="x-default" href="https://example.com/contact/">
A good test is that the block of hreflang lines should be identical on every page in the cluster. If you can paste the English page's block into the German page and it is correct there too, every page includes itself. If the blocks differ from page to page, something is being left out or rewritten.
Why templates leave the current page out
The annotation code was copied from the language switcher
A language switcher shows visitors the other languages they can move to, so it naturally skips the
current one. When a developer reuses the same list to output hreflang, the skip comes along with it.
Look for a condition such as if locale != current, continue on the current
locale, or a filter called something like otherLanguages.
The current page's address is unknown at render time
Some systems store translations as links to other records. The template can look up the addresses of the linked translations but has no ready-made value for the page it is rendering, so it simply outputs the linked ones. The fix is to add the current page to the list before printing it.
Plugin or app settings
Some multilingual plugins and apps have an option for "show current language" in the switcher, and in a few setups that same option also affects the hreflang output. A theme update or plugin setting change can quietly remove the self line from every page.
Hand-written annotations
On small static sites the lines are sometimes typed by hand into each page. It is natural to type "the other versions" and forget the page you are editing, and each page ends up slightly different.
Template fixes for common setups
Plain template logic
The pattern to look for, and its correction, in pseudo-template form:
{# Wrong: borrowed from the language switcher #}
{% for alt in page.translations if alt.locale != page.locale %}
<link rel="alternate" hreflang="{{ alt.locale }}" href="{{ alt.canonical_url }}">
{% endfor %}
{# Right: every version, including this one #}
{% for alt in page.translations_including_self %}
<link rel="alternate" hreflang="{{ alt.locale }}" href="{{ alt.canonical_url }}">
{% endfor %}
If your system has no list that includes the current page, build one: start with the translations, add the current page's locale and canonical address, sort by locale so the output is stable, and print them all.
Hugo
Hugo's .Translations excludes the current page, while .AllTranslations includes
it. Swapping one for the other in the head partial is often the whole fix:
{{ range .AllTranslations }}
<link rel="alternate" hreflang="{{ .Language.LanguageCode }}" href="{{ .Permalink }}">
{{ end }}
Eleventy, Astro and similar static builders
These usually keep a data file or collection mapping each page to its translations. Generate the head lines from that map for all locales, including the current one, rather than filtering by the locale being built.
Frameworks
In Next.js, the alternates.languages object should include the current locale as a key.
In Nuxt, the i18n module's head helper outputs all locales when configured with a base URL. The Next.js
and Nuxt guide has full examples.
WordPress and Shopify
WPML, Polylang and Shopify Markets output self-references by default. If yours are missing, suspect custom theme code that prints its own hreflang, or a second plugin or app doing the same. Remove the custom output and let one tool handle it.
The fix, step by step
Step 1
Confirm which pages lack their own line
Run the Hreflang Checker on a page from each locale and each major page type: home page, category or listing, article or product, and contact or legal pages. Note which pages are missing a self-reference and whether the problem affects every locale or only some. A pattern across one page type points to that template; a pattern across one language points to a locale-specific setting or a plugin configured differently for that language.
Step 2
Find the loop or list that skips the current locale
Open the template or component that prints the hreflang lines and trace where its list of locales comes from. Look for a condition that excludes the current language, a helper named for other languages, or a translations list that by design leaves out the page being rendered. On hand-written static pages, compare the block on each page with its neighbours to spot the gap. Check for a second source of hreflang output too.
Step 3
Include the current locale in the output
Remove the exclusion, or switch to a list that already contains the current page, such
as .AllTranslations in Hugo. Where no such list exists, append the current locale and its
address before printing, then sort by locale so every page outputs the lines in the same order. If the
language switcher shares the same code, split them, so the switcher can hide the current language while
hreflang keeps it.
Step 4
Match the self line to the canonical address
Build the self line from the page's canonical address, not from the incoming request, so query strings, tracking parameters and alternative hosts never leak into it. Check that protocol, host, trailing slash, letter case and the localised slug all match the canonical tag exactly. A self line that differs by a single character is treated as pointing somewhere else, and the checker will still report the self-reference as missing.
Step 5
Retest the template across page types
Deploy, then fetch the raw HTML of one page per locale and per page type and confirm the hreflang block is identical across the cluster and includes each page's own line. Run the Hreflang Checker again for each locale. If self-reference errors are gone but return tag errors remain, those are a separate problem, often revealed only once the self lines are in place, and should be fixed next.
Keeping self-references in place
- Separate the switcher from the annotations. They have different jobs: one hides the current language, the other must include it. Two small functions are safer than one shared one with a flag.
- Test the block's sameness. A simple automated check can fetch every page in a sample cluster, extract the hreflang lines, sort them and compare. Any difference is either a missing self line or a missing return tag.
- Check after plugin and theme updates. A setting that controls "show current language" can change hreflang output without anyone editing templates.
- Never hand-type clusters. Even on small static sites, generate the block from a data file so every page gets the same list.
# Quick sameness check for one cluster (bash)
for u in https://example.com/contact/ https://example.com/de/kontakt/ https://example.com/fr/contact/; do
curl -s "$u" | grep -io 'hreflang="[^"]*" href="[^"]*"' | sort | md5sum
done
# All three hashes should be the same
1. Is a self-referencing hreflang actually required?
Google's hreflang documentation says each language version must list itself as well as all the other versions, so treat it as required. In practice Google can sometimes work out a cluster without it, but a page that leaves itself out is not clearly declaring which language and region it serves within the group, and the cluster becomes less reliable. It costs one line per page, and fixing the template once adds it everywhere, so there is no good reason to leave it out.
2. Is the self-referencing hreflang the same as the canonical tag?
No, although on a correctly set up page they contain the same address. The canonical tag tells search engines which address is the main version of this page. The self-referencing hreflang line tells them which language and region this page serves within its cluster. You need both. They should point to exactly the same address, and building both from one function is the simplest way to keep them identical as the site changes.
3. Our language switcher hides the current language. Is that why the self line is missing?
Very likely. A language switcher is meant to show only the languages a visitor can move to, so it skips the current one. When the hreflang output is built from the same list or the same function, it inherits that skip. Keep the switcher behaving as it does for visitors, but give the hreflang output its own list that includes every locale, including the one being displayed. That separation also stops future switcher changes from affecting search.
4. The German line is there. Why does the checker still say the self-reference is missing?
Because its address is not exactly the page's own address. Common differences are a missing or extra trailing slash, http instead of https, www on one and not the other, capital letters, the English slug used with a German prefix, or a tracking parameter copied from the visit. To a search engine these are different URLs, so the page is effectively naming another page. Build the line from the canonical address and the difference disappears.
5. Does the x-default page need a self-referencing line too?
The x-default page should carry the same block as every other page, which includes the x-default line pointing to itself. If the x-default address is also one of your language versions, such as the English page doubling as the fallback, that page will list itself twice: once with its language code and once as x-default. That is correct and expected. If it is a separate language picker page, it lists itself once as x-default and names all the language versions.
6. Do self-references matter in an hreflang sitemap?
Yes. In a sitemap, each url entry carries its own set of alternate links, and that set must include the entry's own address alongside the others, just as the HTML block would. Sitemap generators that loop over other languages for each address can skip the current one in the same way templates do. Check a few entries in the file and make sure each one lists its own loc address among its alternates.
The Hreflang Checker follows each cluster of locale pages and validates return tags, language codes, x-default, sitemap consistency, target reachability and canonical alignment, so pages that leave themselves out of their own cluster are flagged.
Open Hreflang Fixes โ