/ โ† Hreflang Fixes / Self-Reference
In this guide
  1. Why a page has to name itself
  2. What a missing self line looks like
  3. Self lines that are present but do not count
  4. Why templates leave the current page out
  5. Template fixes for common setups
  6. The fix, step by step
  7. Keeping self-references in place
  8. Questions and answers

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.

๐Ÿ’ก A missing self-reference is different from a missing return tag. A return tag is page B naming page A after A named B. A self-reference is page A naming page A. The return tags guide covers the first; this guide covers the second.

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.

Self lines that are present but do not count

The checker also reports a missing self-reference when a line exists but its address is not the page's own. These are harder to see, because a quick read of the source shows a line for the right language.

Page addressSelf line points toWhy it does not count
https://example.com/de/kontakt/https://example.com/de/kontaktMissing trailing slash: a different URL
https://example.com/de/kontakt/http://example.com/de/kontakt/Different protocol
https://example.com/de/kontakt/https://example.com/de/Points to the locale home page, not this page
https://example.com/de/kontakt/https://example.com/de/contact/Uses the English slug with a German prefix, a page that may not exist
https://example.com/de/kontakt/?ref=navhttps://example.com/de/kontakt/?ref=navCopies a tracking parameter from the request into the tag

The last row is worth a note. Templates that build the self line from the incoming request address, rather than from the page's stored address, echo whatever query string the visitor arrived with. The line then differs from the canonical on every visit that carries a parameter. Always build it from the canonical address instead.

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

# 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
Questions and answers

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.

Check every page lists itself

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 โ†’
๐Ÿ’ฌ Got a problem?
Related Guides: All Hreflang Fixes  ·  Fixing Missing Return Tags  ·  Hreflang and Canonical Conflicts  ·  Hreflang Guide  ·  Learning Hub

About aiwebpageseo

aiwebpageseo.com is a data-driven SEO and AEO (Answer Engine Optimisation) platform providing a free suite of technical SEO tools. Rather than relying on AI-theorised assumptions, the platform analyses live URL performance, delivering objective diagnostics, page speed metrics, CLS debugging, and site crawl data alongside actionable technical tutorials.