Development & Building with AI · Explainer
Internationalization and hreflang in Astro projects
Hreflang tells Google that two pages are the same thing in different languages. There are only a few rules, but a single reciprocity error makes the search engine ignore all of them.
- Author:
- Redação Token & Tech
- Responsible editor:
- Daniel Ferreira
- Translation:
- Redação Token & Tech
Published · 7 min read
Originally published in Brazilian Portuguese.
Translated from the Portuguese original with AI assistance.
Also available in: Português
Key points
- Each version must list itself and all the others, with full URLs; if a page does not point back, Google ignores the annotations.
- Codes follow ISO 639-1 for language and, optionally, ISO 3166-1 for region; x-default indicates the page for users who do not match any version.
- Astro's i18n routing does not generate hreflang on its own: you build the list of translations and use it in the head and in the sitemap.
You have published an article in Portuguese and its English translation. To the reader, the relationship is obvious. To a search engine, they are two different URLs with different text. You need to state explicitly that one is the localized version of the other.
That is the job of hreflang: an annotation that links equivalent pages in different languages or regions. This article explains Google’s official rules, how they relate to the HTML lang attribute and how to generate the annotations in an Astro project. The code samples are minimal examples.
If you are still setting up the site structure, start with the tutorial how to build an international website with Astro.
What hreflang does and does not do
According to Google’s documentation on localized versions of a page, hreflang indicates that a set of pages are localized variants of the same content. This lets the search engine show users the version in their language.
Hreflang is not used to detect language. The same page states that Google does not use hreflang or the HTML lang attribute to identify a page’s language. That is done by algorithms that analyze the text. In practice: if the page at /pt-br/ has its body in English, no annotation will fix that.
The documentation also clarifies when versions in different languages count as duplicate content: only when the main content remains untranslated. Translating only the menu and footer does not create a localized version.
The three ways to declare it
Google accepts three methods and says they are equivalent from its point of view. Using more than one brings no advantage in search.
- HTML tags.
<link rel="alternate" hreflang="...">elements inside the<head>. - HTTP header. A
Linkheader in the server response, with the same set of versions. - XML sitemap. Each
<url>in the sitemap lists its variants withxhtml:linkelements.
On static sites, HTML tags and the sitemap are the most straightforward options. What matters is that both sources say exactly the same thing, if you use both.
The rules that trip up most projects
Reciprocity
This is the central rule. Each language version must list itself and all other versions. If two pages do not point to each other, Google ignores the annotations. The set of links must be identical across all versions.
Example, for a page with an English version and a Brazilian Portuguese version:
<!-- example: the same block appears on both pages -->
<link rel="alternate" hreflang="en" href="https://example.com/en/what-is-ai/" />
<link rel="alternate" hreflang="pt-BR" href="https://example.com/pt-br/o-que-e-ia/" />
<link rel="alternate" hreflang="x-default" href="https://example.com/en/what-is-ai/" />
Full URLs
URLs must include the protocol and domain. https://example.com/en/ works. /en/ and //example.com/en/ do not. Versions can be on different domains.
Language and region codes
The first code is the language, in ISO 639-1 format (en, pt, es). An optional second code indicates the region, in ISO 3166-1 alpha-2 format (BR, PT, US). Some points from Google’s documentation:
- Region alone is not valid.
BRis not a valid value;pt-BRis. - Codes outside these standards, such as
es-419(Latin American Spanish), are not accepted. - Values are not case-sensitive, but by convention the region is written in uppercase.
- Reserved codes such as
EU,UNandUKhave no effect. The United Kingdom isGB.
The W3C, the consortium that maintains web standards, gives a complementary rule in its guide on choosing a language tag: use the shortest possible tag and add a region only when it carries necessary information. If you only have one Portuguese version, pt may be enough. If you want to make clear it is Brazilian Portuguese, pt-BR is valid for both the W3C and Google.
x-default: the page for everyone else
The x-default value is not a language. According to Google, it indicates the fallback page for users whose language settings do not match any available version. The documentation describes it as intended mainly for language selector pages.
When the site has no selector page, one option is to point x-default to the main edition. That is Token & Tech’s choice, described below.
Canonical and hreflang together
The canonical tag (rel="canonical") indicates which URL is the main version when there are duplicate pages. The classic mistake on multilingual sites is pointing the canonical of the Portuguese version to the English version. That tells the search engine the Portuguese page is a copy.
Google’s guide on canonical URLs advises, when using hreflang, to specify a canonical page in the same language, or the best possible substitute if one does not exist. The same guide says that, for canonicalization purposes, Google prefers URLs that are part of hreflang clusters. In practice, each translation points its canonical to itself.
lang and hreflang are not the same thing
The lang attribute declares the language of the page’s own content. The W3C recommends always using it on the html tag, with BCP 47 tags, and marking passages in another language with lang on the element that wraps them. For right-to-left languages such as Arabic, the dir attribute is also required.
Hreflang, in <link rel="alternate">, describes another page. The two complement each other. A page in Brazilian Portuguese would have <html lang="pt-BR"> and, in the <head>, links to its versions in other languages.
How to generate hreflang in Astro
The Astro i18n routing guide handles language prefixes, localized links and fallback. In the version consulted on October 8, 2026, it does not describe automatic hreflang generation. You need to build it yourself. The documentation’s i18n recipe shows how to organize content and routes by language, which is the foundation for this.
The starting point is to have, for each page, the list of its translations. A simple way is to give each translation a shared identifier (for example, a group field in the content collection) and, at build time, gather the pages that share that value.
With the list in hand, a component can generate the tags:
---
// src/components/Hreflang.astro: minimal example
// Requires `site` to be set in astro.config.mjs.
interface Props {
alternates: { hreflang: string; path: string }[]; // includes the page itself
xDefaultPath?: string;
}
const { alternates, xDefaultPath } = Astro.props;
const abs = (path: string) => new URL(path, Astro.site).href;
---
{alternates.length > 1 && alternates.map((a) => (
<link rel="alternate" hreflang={a.hreflang} href={abs(a.path)} />
))}
{alternates.length > 1 && xDefaultPath && (
<link rel="alternate" hreflang="x-default" href={abs(xDefaultPath)} />
)}
Two choices in this example deserve explanation. The abs function turns the path into a full URL, as Google requires. And the alternates.length > 1 condition avoids emitting hreflang for pages that exist in only one language.
In the sitemap, the same list becomes xhtml:link elements, with the namespace declared at the top:
<!-- example: sitemap.xml excerpt -->
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"
xmlns:xhtml="http://www.w3.org/1999/xhtml">
<url>
<loc>https://example.com/en/what-is-ai/</loc>
<xhtml:link rel="alternate" hreflang="en" href="https://example.com/en/what-is-ai/"/>
<xhtml:link rel="alternate" hreflang="pt-BR" href="https://example.com/pt-br/o-que-e-ia/"/>
<xhtml:link rel="alternate" hreflang="x-default" href="https://example.com/en/what-is-ai/"/>
</url>
<url>
<loc>https://example.com/pt-br/o-que-e-ia/</loc>
<xhtml:link rel="alternate" hreflang="en" href="https://example.com/en/what-is-ai/"/>
<xhtml:link rel="alternate" hreflang="pt-BR" href="https://example.com/pt-br/o-que-e-ia/"/>
<xhtml:link rel="alternate" hreflang="x-default" href="https://example.com/en/what-is-ai/"/>
</url>
</urlset>
Each <url> repeats the full set, including itself. That is reciprocity applied to the sitemap.
Language-based redirects: why to avoid them
It is common to want to detect the browser’s language and redirect the visitor. Google’s guide on multilingual sites advises against automatic redirects based on a guess about the language, and also against adapting content by IP. The technical reason: Google’s crawler usually crawls from the United States and without the Accept-Language header. If the site decides for the visitor, parts of it may never be found. The recommendation is to offer links to the other versions and leave the choice to the reader.
Documented case: Token & Tech’s hreflang
This publication’s website is built with Astro and generates hreflang from its own router, not from native i18n routing. According to the project’s technical documentation:
- Each page has a group identifier, shared by its translations, and a flag marking it as indexable or not.
- A page’s alternates are the other pages in the same group that are also indexable.
noindexpages neither receive nor appear in hreflang. x-defaultpoints to the Portuguese version, the site’s original and main language, served at the root (no /pt-br/). If the page does not exist in Portuguese, there is nox-default.- The tags used are
enandpt-BR, while the URLs use/en/and/pt-br/. - The
<head>tags and the sitemap are generated from the same list of routes, which prevents them from saying different things. - A page exists in a language only when a real translation exists. No version is generated with text in another language.
Common mistakes, in one list
The following five items follow directly from the Google rules cited above:
- A page that does not point back to the other (no reciprocity).
- A page that does not include itself in its own list.
- Relative URLs instead of full ones.
- A region code on its own or an invalid one, such as
UK. - A translation’s canonical pointing to another language.
In the editorial team’s assessment, two other frequent oversights are also worth avoiding: listing noindex, redirecting or nonexistent pages in hreflang, and publishing untranslated main content under another language’s URL.
Key takeaways
- Hreflang links equivalent versions; it does not detect language or replace a real translation.
- Reciprocity, full URLs and correct ISO codes are the three rules that matter most.
- In Astro, generate hreflang from a single list of translations and use that list in the
<head>and in the sitemap. - Canonical always in the same language,
langon the html tag and no automatic redirects based on the visitor’s language.
Disclosure: The Token & Tech website is built with Astro and uses the multilingual architecture described here.
Sources
- Tell Google about localized versions of your page, Google Search Central. Accessed October 8, 2026
- How to specify a canonical URL with rel="canonical" and other methods, Google Search Central. Accessed October 8, 2026
- Managing multi-regional and multilingual sites, Google Search Central. Accessed October 8, 2026
- Declaring language in HTML, W3C Internationalization. Accessed October 8, 2026
- Choosing a language tag, W3C Internationalization. Accessed October 8, 2026
- Internationalization (i18n) Routing, Astro Docs. Accessed October 8, 2026
- Add i18n features, Astro Docs. Accessed October 8, 2026