---
title: "Hreflang Tags in Vue"
description: "Map corresponding translated pages to hreflang URLs in Vue. Verify self links, return links, canonical choices, and reactive Unhead output."
canonical_url: "https://nuxtseo.com/learn-seo/vue/routes-and-rendering/i18n"
last_updated: "2026-10-05"
---

> Index of every page on this site: [llms.txt](https://nuxtseo.com/llms.txt)

::key-takeaways
- 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-default` identifies 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:

```html
<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](https://developers.google.com/search/docs/specialty/international/localized-versions) 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()`{lang="ts"} in component setup, [install Unhead and its Vue plugin](https://unhead.unjs.io/docs/vue/head/guides/get-started/installation). This excerpt covers the same two published landing pages:

```ts [Unhead]
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](https://developers.google.com/search/docs/specialty/international/localized-versions) 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](https://developers.google.com/search/docs/specialty/international/localized-versions) 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](https://vue-i18n.intlify.dev/guide/advanced/composition) provides translations and locale state. Follow its installation guide and configure `createI18n({ legacy: false, ... })`{lang="ts"} before using `useI18n()`{lang="ts"} 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:

```ts
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](https://developers.google.com/search/docs/specialty/international/localized-versions), particularly for language selectors. It does not perform a browser redirect.

```html
<!-- 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

```ts
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](https://developers.google.com/search/docs/specialty/international/localized-versions) is one of the mistakes Google calls out directly.

```html
<!-- 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:

```html
<!-- 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:

```ts
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.

```html
<!-- ✅ 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" />
```

```html
<!-- ❌ 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()`{lang="ts"} callback does not run on the server. Inspect both initial and rendered HTML when the app depends on JavaScript. See the [prerendering guide](https://nuxtseo.com/learn-seo/vue/spa/prerendering) 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:

```vue
<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:

```http
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
<?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

```html
<!-- ❌ 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

```html
<!-- ❌ 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

```html
<!-- ❌ 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

```html
<!-- 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](https://support.google.com/webmasters/answer/12474899). 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](https://hreflang.org)
- [Merkle Hreflang Checker](https://technicalseo.com/tools/hreflang/)
- 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:

```vue
<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:

```ts
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](https://nuxtseo.com/learn-seo/vue/mastering-meta). For SSR, create fresh app, router, and head instances per request. The [Vite SSR guide](https://nuxtseo.com/learn-seo/vue/ssr-frameworks/vite-ssr) shows the server integration. These excerpts do not install Vue I18n, fetch translations, serve routes, or transfer initial state.

## Checklist

::checklist{#vue-hreflang}
- 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-default` identifies 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](https://i18n.nuxtjs.org/docs/guide/seo/) provides locale-aware head helpers. Its SEO setup requires configuration, including locale language codes and an appropriate base URL. [Learn more in Nuxt →](https://nuxtseo.com/learn-seo/nuxt/routes-and-rendering)

## Sitemap

See the full [sitemap](https://nuxtseo.com/sitemap.md) for all pages.
