Hreflang Tags in Vue
- Hreflang identifies corresponding language or regional pages. It does not guarantee which version Google shows
- Every page in a hreflang cluster needs a self-referencing tag and must link back to every alternate version it references
- An optional
x-defaultidentifies a fallback for unmatched languages. Hreflang does not redirect visitors
Hreflang identifies corresponding language or regional versions of a page. Google can discover these versions without annotations. Fully translated main content is not a duplicate merely because it describes the same topic. Hreflang does not replace translated content or guarantee a particular search result.
Emit this complete set on both corresponding English and French landing pages:
<head>
<link rel="alternate" hreflang="en" href="https://example.com/en" />
<link rel="alternate" hreflang="fr" href="https://example.com/fr" />
<link rel="alternate" hreflang="x-default" href="https://example.com/en" />
</head>Google recommends hreflang when you have:
- Same content in different languages
- Regional variations (en-US vs en-GB)
- Partial translations mixed with original language
Hreflang is a signal, not a directive. Search engines can ignore it if they think a different version better matches user intent.
Quick Reference
Before calling useHead() in component setup, install Unhead and its Vue plugin. This excerpt covers the same two published landing pages:
import { useHead } from '@unhead/vue'
useHead({
link: [
{ rel: 'alternate', hreflang: 'en', href: 'https://example.com/en' },
{ rel: 'alternate', hreflang: 'fr', href: 'https://example.com/fr' },
{ rel: 'alternate', hreflang: 'x-default', href: 'https://example.com/en' }
]
})Hreflang Format
Hreflang values combine an ISO 639-1 language code with an optional ISO 3166-1 Alpha-2 region code:
| Format | Example | Use Case |
|---|---|---|
| Language only | en | English content for all regions |
| Language + region | en-US | English for United States |
| Language + region | en-GB | English for United Kingdom |
| Special fallback | x-default | Default for unmatched languages/regions |
Google treats hreflang values as case-insensitive. Use conventional lowercase language codes and uppercase regions, separated by a hyphen: en-US.
Common Examples
en English (all regions)
fr French (all regions)
es Spanish (all regions)
en-US English for USA
en-GB English for UK
fr-CA French for Canada
es-MX Spanish for Mexico
zh-CN Simplified Chinese (China)
zh-TW Traditional Chinese (Taiwan)Region Codes Are Tricky
UK isn't a valid ISO 3166-1 region code: use GB for the United Kingdom instead. For Chinese, Google's own hreflang examples use zh-TW (the script is inferred as Traditional) or the explicit script codes zh-Hant and zh-Hans, since ISO 639-1 only defines one zh language code.
Implementation in Vue
With Unhead
Avoid prefixing the current localized path with another locale. Slugs can differ between translations, such as English /en/about and French /fr/a-propos. Store the actual corresponding URLs by page identity.
The composable below emits that mapping. Install Unhead's plugin first. Supply the current page identity from your router or data layer, and include only published counterparts.
With vue-i18n
Vue I18n's Composition API provides translations and locale state. Follow its installation guide and configure createI18n({ legacy: false, ... }) before using useI18n() in component setup.
An available message locale does not prove that a translated URL exists. Keep the URL mapping separate from the message catalog. When the route changes, update the translation locale and supply the matching page identity to the head composable.
Creating a Composable
Save this as useHreflang.ts. This two-language example has a published translation for each page. Add or remove entries to reflect your actual content:
import { useHead } from '@unhead/vue'
export type PageId = 'home' | 'about'
export type Locale = 'en' | 'fr'
export const translatedPages = {
home: { en: 'https://example.com/en', fr: 'https://example.com/fr' },
about: {
en: 'https://example.com/en/about',
fr: 'https://example.com/fr/a-propos'
}
} satisfies Record<PageId, Record<Locale, string>>
export function useHreflang(page: () => PageId) {
useHead(() => {
const urls = translatedPages[page()]
return {
link: [
{ rel: 'alternate', hreflang: 'en', href: urls.en },
{ rel: 'alternate', hreflang: 'fr', href: urls.fr },
{ rel: 'alternate', hreflang: 'x-default', href: urls.en }
]
}
})
}The getter reads the current page identity when it changes. Emit the same set on each corresponding translation. The English fallback points to the equivalent English page, rather than sending every unmatched-language result to the home page.
X-Default Tag
The optional x-default identifies a fallback when no language or region matches. Google recommends considering a fallback, particularly for language selectors. It does not perform a browser redirect.
<!-- Fallback annotation for unmatched languages -->
<link rel="alternate" hreflang="x-default" href="https://example.com/en" />Set x-default to:
- Your primary language version
- A language selector page
- A page suitable for unmatched-language visitors
import { useHead } from '@unhead/vue'
// Point x-default to language selector
useHead({
link: [
{ rel: 'alternate', hreflang: 'en-US', href: 'https://example.com/en-us' },
{ rel: 'alternate', hreflang: 'en-GB', href: 'https://example.com/en-gb' },
{ rel: 'alternate', hreflang: 'fr-FR', href: 'https://example.com/fr-fr' },
{ rel: 'alternate', hreflang: 'x-default', href: 'https://example.com/choose-language' }
]
})Hreflang Rules
1. Bidirectional Links (Return Links)
Every page referenced in hreflang must link back: if page A links to page B, page B must link to page A. Missing return links is one of the mistakes Google calls out directly.
<!-- en page must reference fr page -->
<link rel="alternate" hreflang="fr" href="https://example.com/fr" />
<!-- fr page must reference en page -->
<link rel="alternate" hreflang="en" href="https://example.com/en" />2. Self-Referential Links
Each page must include a hreflang tag pointing to itself:
<!-- On the English page -->
<link rel="alternate" hreflang="en" href="https://example.com/en" />
<link rel="alternate" hreflang="fr" href="https://example.com/fr" />
<!-- On the French page -->
<link rel="alternate" hreflang="en" href="https://example.com/en" />
<link rel="alternate" hreflang="fr" href="https://example.com/fr" />3. Use Canonical URLs Only
For the counterparts you want Google to process, use URLs that:
- Return HTTP 200 status
- Be indexable (no
noindex) - Not be blocked by robots.txt
- Point to canonical URLs (not redirects)
Use reachable, indexable final URLs that represent the corresponding content. A blocked or excluded URL can prevent Google from processing that counterpart; it does not prove every other pair fails.
This setup excerpt describes the fully translated English product page. Emit the same alternate set, with the French canonical, on its French counterpart:
import { useHead } from '@unhead/vue'
useHead({
link: [
{ rel: 'canonical', href: 'https://example.com/en/products' },
{ rel: 'alternate', hreflang: 'en', href: 'https://example.com/en/products' },
{ rel: 'alternate', hreflang: 'fr', href: 'https://example.com/fr/produits' }
]
})If an old URL redirects, reference its final counterpart instead.
4. Canonical vs Hreflang
Canonical tags identify a preferred representative of duplicate or near-identical content. Hreflang identifies localized counterparts. For fully translated pages, use a canonical for that language version. If the main content remains untranslated, assess the appropriate canonical separately.
<!-- ✅ Correct: each language version is canonical to itself -->
<!-- English page -->
<link rel="canonical" href="https://example.com/en/about" />
<link rel="alternate" hreflang="en" href="https://example.com/en/about" />
<link rel="alternate" hreflang="fr" href="https://example.com/fr/a-propos" />
<!-- French page -->
<link rel="canonical" href="https://example.com/fr/a-propos" />
<link rel="alternate" hreflang="en" href="https://example.com/en/about" />
<link rel="alternate" hreflang="fr" href="https://example.com/fr/a-propos" /><!-- ❌ Wrong: canonical pointing between languages -->
<!-- French page -->
<link rel="canonical" href="https://example.com/en/about" />
<!-- This asks Google to prefer the English URL instead -->Delivering Hreflang
Google supports HTML tags, HTTP headers, and sitemaps. SSR is not required for all these methods. Static HTML or a sitemap can describe counterparts without runtime rendering.
If you use SSR or generation, emit head tags during setup so they appear in the response. An onMounted() callback does not run on the server. Inspect both initial and rendered HTML when the app depends on JavaScript. See the prerendering guide for build-time output.
Implementation Methods
Google considers HTML tags, HTTP headers, and XML sitemaps equivalent methods. Choose the method your site can maintain. Using several methods gives no additional Search benefit; keep them consistent if you do.
HTML Link Tags (Recommended)
This component-setup excerpt describes corresponding US and UK landing pages. Emit the same set on both pages:
<script setup lang="ts">
import { useHead } from '@unhead/vue'
useHead({
link: [
{ rel: 'alternate', hreflang: 'en-US', href: 'https://example.com/us' },
{ rel: 'alternate', hreflang: 'en-GB', href: 'https://example.com/uk' },
{ rel: 'alternate', hreflang: 'x-default', href: 'https://example.com/us' }
]
})
</script>HTTP Headers
HTTP Link headers can describe non-HTML resources such as PDFs. Set this complete header on both corresponding English and French PDF responses through your host or server:
Link: <https://example.com/en/document.pdf>; rel="alternate"; hreflang="en", <https://example.com/fr/document.pdf>; rel="alternate"; hreflang="fr", <https://example.com/en/document.pdf>; rel="alternate"; hreflang="x-default"The file-serving handler and response configuration belong to your host. Verify the actual PDF response headers, rather than assuming a Vue component can set them.
XML Sitemap
Add hreflang to your sitemap if you can't modify HTML/headers:
<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"
xmlns:xhtml="http://www.w3.org/1999/xhtml">
<url>
<loc>https://example.com/en</loc>
<xhtml:link rel="alternate" hreflang="en" href="https://example.com/en" />
<xhtml:link rel="alternate" hreflang="fr" href="https://example.com/fr" />
<xhtml:link rel="alternate" hreflang="x-default" href="https://example.com/en" />
</url>
<url>
<loc>https://example.com/fr</loc>
<xhtml:link rel="alternate" hreflang="en" href="https://example.com/en" />
<xhtml:link rel="alternate" hreflang="fr" href="https://example.com/fr" />
<xhtml:link rel="alternate" hreflang="x-default" href="https://example.com/en" />
</url>
</urlset>Common Mistakes
Missing Return Links
<!-- ❌ English page links to French, but French doesn't link back -->
<!-- en page -->
<link rel="alternate" hreflang="fr" href="https://example.com/fr" />
<!-- fr page -->
<!-- Missing hreflang links! -->Non-Canonical URLs
<!-- ❌ Linking to URL that redirects -->
<link rel="alternate" hreflang="en" href="https://example.com/en-us" />
<!-- But /en-us redirects to /us -->
<!-- ✅ Use final destination -->
<link rel="alternate" hreflang="en" href="https://example.com/us" />Noindex or Blocked Pages
<!-- ❌ Hreflang on noindex page -->
<meta name="robots" content="noindex" />
<link rel="alternate" hreflang="fr" href="https://example.com/fr" />All pages in a hreflang cluster must be indexable and crawlable. Remove noindex, unblock the URL in robots.txt, or remove the page from hreflang annotations altogether.
Mixing with Canonical
<!-- Fully translated French page: prefer its French URL -->
<!-- French page -->
<link rel="canonical" href="https://example.com/en" />
<link rel="alternate" hreflang="fr" href="https://example.com/fr" />
<!-- This canonical asks Google to prefer EN instead of the French URL -->Testing Hreflang
Manual Inspection
View page source and verify:
- All alternate links are present
- Self-referential link exists
- If used, x-default points to the intended fallback
- All URLs return 200
- All URLs are canonical (not redirects)
Google Search Console
The International Targeting report is deprecated. Google still supports hreflang. Inspect the actual tags and counterpart URLs directly. URL Inspection can help check a page's fetch and indexing state, but it is not a replacement hreflang-cluster report.
Third-Party Tools
- Hreflang Tags Testing Tool
- Merkle Hreflang Checker
- A crawler that checks reciprocal alternate sets
Worked Example: Translated Pages
Save this component as TranslatedPage.vue beside useHreflang.ts. It demonstrates reactive annotations and canonicals. Replace its illustrative heading with your translated page content:
<script setup lang="ts">
import type { Locale, PageId } from './useHreflang'
import { useHead } from '@unhead/vue'
import { translatedPages, useHreflang } from './useHreflang'
const props = defineProps<{ page: PageId, locale: Locale }>()
useHreflang(() => props.page)
useHead({
link: [{ rel: 'canonical', href: () => translatedPages[props.page][props.locale] }]
})
</script>
<template>
<main>
<h1>{{ props.page }}</h1>
</main>
</template>In an installed Vue Router app, this route excerpt supplies page identity and locale as props:
import TranslatedPage from './TranslatedPage.vue'
const routes = [
{ path: '/en', component: TranslatedPage, props: { page: 'home', locale: 'en' } },
{ path: '/fr', component: TranslatedPage, props: { page: 'home', locale: 'fr' } },
{ path: '/en/about', component: TranslatedPage, props: { page: 'about', locale: 'en' } },
{ path: '/fr/a-propos', component: TranslatedPage, props: { page: 'about', locale: 'fr' } }
]Create the router and install it, along with the Unhead plugin, using the Vue setup guide. For SSR, create fresh app, router, and head instances per request. The Vite SSR guide shows the server integration. These excerpts do not install Vue I18n, fetch translations, serve routes, or transfer initial state.
Checklist
- Every page includes a self-referencing hreflang tag plus links to all alternate versions
- Every alternate page links back (return links) to close the cluster
- If used,
x-defaultidentifies a suitable fallback for unmatched languages - Hreflang URLs return 200, are indexable, and aren't blocked by robots.txt
- Canonical choices reflect the content; fully translated pages use a preferred URL for their own language
- The chosen HTML, header, or sitemap method contains the intended annotations; inspect actual output
Using Nuxt? @nuxtjs/i18n provides locale-aware head helpers. Its SEO setup requires configuration, including locale language codes and an appropriate base URL. Learn more in Nuxt →
Pre-launch setup, post-launch verification, and ongoing monitoring. Interactive checklist with links to every guide.