---
title: "Hreflang Tags in Nuxt"
description: "Configure hreflang in Nuxt with useHead or @nuxtjs/i18n so Google shows the right language version instead of flagging duplicate content."
canonical_url: "https://nuxtseo.com/learn-seo/nuxt/routes-and-rendering/i18n"
last_updated: "2026-07-16"
---

<key-takeaways>

- Hreflang tags tell search engines which language or regional version of a page to serve, keeping localized content out of duplicate-content trouble
- Every page in a hreflang cluster needs a self-referencing tag and must link back to every alternate version it references
- Set `x-default` so visitors whose language doesn't match any configured version land somewhere sensible
- `@nuxtjs/i18n` generates hreflang automatically for every configured locale, including return links and `x-default`

</key-takeaways>

Hreflang tags tell search engines which language version of your page to show users. Without them, Google might show your French content to English speakers or rank the wrong regional version, creating [duplicate content](/learn-seo/nuxt/controlling-crawlers/duplicate-content) issues across languages.

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

```ts [useHead]
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:

<table>
<thead>
  <tr>
    <th>
      Format
    </th>
    
    <th>
      Example
    </th>
    
    <th>
      Use Case
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      Language only
    </td>
    
    <td>
      <code>
        en
      </code>
    </td>
    
    <td>
      English content for all regions
    </td>
  </tr>
  
  <tr>
    <td>
      Language + region
    </td>
    
    <td>
      <code>
        en-US
      </code>
    </td>
    
    <td>
      English for United States
    </td>
  </tr>
  
  <tr>
    <td>
      Language + region
    </td>
    
    <td>
      <code>
        en-GB
      </code>
    </td>
    
    <td>
      English for United Kingdom
    </td>
  </tr>
  
  <tr>
    <td>
      Special fallback
    </td>
    
    <td>
      <code>
        x-default
      </code>
    </td>
    
    <td>
      Default for unmatched languages/regions
    </td>
  </tr>
</tbody>
</table>

Language codes must be lowercase. Region codes must be uppercase. Separate with hyphen: `en-US`.

### Common Examples

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

## Manual Implementation

If you're not using @nuxtjs/i18n, set hreflang manually with `useHead()`:

```vue
<script setup lang="ts">
const route = useRoute()

// Build alternate URLs based on current route
const alternates = [
  { lang: 'en', url: `https://example.com/en${route.path}` },
  { lang: 'fr', url: `https://example.com/fr${route.path}` },
  { lang: 'de', url: `https://example.com/de${route.path}` }
]

useHead({
  link: [
    ...alternates.map(alt => ({
      rel: 'alternate',
      hreflang: alt.lang,
      href: alt.url
    })),
    { rel: 'alternate', hreflang: 'x-default', href: 'https://example.com/en' }
  ]
})
</script>
```

Extract hreflang logic into a reusable composable at `composables/useHreflang.ts` rather than repeating it on every page:

```ts
// composables/useHreflang.ts - auto-imported in Nuxt

export function useHreflang(locales: string[]) {
  const route = useRoute()

  const links = computed(() => {
    const baseUrl = 'https://example.com'

    return [
      ...locales.map(locale => ({
        rel: 'alternate',
        hreflang: locale,
        href: `${baseUrl}/${locale}${route.path}`
      })),
      {
        rel: 'alternate',
        hreflang: 'x-default',
        href: `${baseUrl}/${locales[0]}${route.path}`
      }
    ]
  })

  useHead({ link: links })
}
```

Use it in pages:

```vue
<script setup lang="ts">
useHreflang(['en', 'fr', 'de', 'es'])
</script>
```

## Automatic Implementation with @nuxtjs/i18n

The [@nuxtjs/i18n](https://i18n.nuxtjs.org/) module automatically generates hreflang tags for all configured locales. Nuxt handles return links, self-referential tags, and x-default automatically.

```ts
// nuxt.config.ts
export default defineNuxtConfig({
  modules: ['@nuxtjs/i18n'],

  i18n: {
    locales: [
      { code: 'en', iso: 'en-US', file: 'en.json' },
      { code: 'fr', iso: 'fr-FR', file: 'fr.json' },
      { code: 'de', iso: 'de-DE', file: 'de.json' }
    ],
    defaultLocale: 'en',
    strategy: 'prefix', // URLs like /en, /fr, /de

    // SEO features
    baseUrl: 'https://example.com',
    detectBrowserLanguage: {
      useCookie: true,
      cookieKey: 'i18n_redirected',
      redirectOn: 'root'
    }
  }
})
```

Nuxt automatically generates:

```html
<!-- On /en/about -->
<link rel="alternate" hreflang="en-US" href="https://example.com/en/about" />
<link rel="alternate" hreflang="fr-FR" href="https://example.com/fr/about" />
<link rel="alternate" hreflang="de-DE" href="https://example.com/de/about" />
<link rel="alternate" hreflang="x-default" href="https://example.com/en/about" />
```

### URL Strategies

The i18n module supports different URL patterns:

<table>
<thead>
  <tr>
    <th>
      Strategy
    </th>
    
    <th>
      Example
    </th>
    
    <th>
      Notes
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      <code>
        prefix
      </code>
    </td>
    
    <td>
      <code>
        /en/about
      </code>
      
      , <code>
        /fr/about
      </code>
    </td>
    
    <td>
      Default locale also gets prefix
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        prefix_except_default
      </code>
    </td>
    
    <td>
      <code>
        /about
      </code>
      
      , <code>
        /fr/about
      </code>
    </td>
    
    <td>
      Default locale has no prefix
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        prefix_and_default
      </code>
    </td>
    
    <td>
      <code>
        /about
      </code>
      
      , <code>
        /en/about
      </code>
      
      , <code>
        /fr/about
      </code>
    </td>
    
    <td>
      Both work for default
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        no_prefix
      </code>
    </td>
    
    <td>
      <code>
        /about
      </code>
    </td>
    
    <td>
      Locale in cookie/domain only
    </td>
  </tr>
</tbody>
</table>

```ts
// nuxt.config.ts
export default defineNuxtConfig({
  i18n: {
    strategy: 'prefix_except_default', // /about (en), /fr/about
    defaultLocale: 'en'
  }
})
```

### Domain-Based Locales

For sites with different domains per language:

```ts
// nuxt.config.ts
export default defineNuxtConfig({
  i18n: {
    locales: [
      { code: 'en', iso: 'en-US', domain: 'example.com' },
      { code: 'fr', iso: 'fr-FR', domain: 'example.fr' },
      { code: 'de', iso: 'de-DE', domain: 'example.de' }
    ],
    strategy: 'no_prefix',
    differentDomains: true
  }
})
```

Generates:

```html
<link rel="alternate" hreflang="en-US" href="https://example.com/about" />
<link rel="alternate" hreflang="fr-FR" href="https://example.fr/about" />
<link rel="alternate" hreflang="de-DE" href="https://example.de/about" />
```

## X-Default Tag

The `x-default` hreflang provides a fallback URL when no language matches the user's preferences. [Google recommends setting it](https://developers.google.com/search/docs/specialty/international/localized-versions) whenever a hreflang cluster doesn't cover every possible visitor.

```html
<!-- User with no matching language sees this -->
<link rel="alternate" hreflang="x-default" href="https://example.com/en" />
```

Set x-default to:

- Your primary language version
- A language selector page
- The most universally understood language version

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

All hreflang URLs must:

- Return HTTP 200 status
- Be indexable (no `noindex`)
- Not be blocked by robots.txt
- Point to canonical URLs (not redirects)

A hreflang URL Google can't crawl or index gets dropped from the cluster, which breaks the return-link chain for every other page in it.

```vue
<script setup lang="ts">
// ✅ Good - points to canonical URL
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' }
  ]
})

// ❌ Bad - points to redirect
useHead({
  link: [
    { rel: 'alternate', hreflang: 'en', href: 'https://example.com/old-url' } // redirects to /en/products
  ]
})
</script>
```

### 4. Canonical vs Hreflang

Canonical tags and hreflang serve different purposes: don't use canonical to point between language versions, since [that signals duplicate content](https://developers.google.com/search/docs/specialty/international/localized-versions) rather than translations.

```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 tells Google the French page is duplicate content -->
```

## Server-Side Rendering Required

Hreflang tags must exist in the initial HTML response. Setting them from `onMounted()` runs after the server has already sent its response, so search engines relying on the initial HTML never see them:

```vue
<script setup lang="ts">
// ❌ Runs after SSR, search engines may miss this
onMounted(() => {
  useHead({
    link: [
      { rel: 'alternate', hreflang: 'en', href: 'https://example.com/en' }
    ]
  })
})

// ✅ Runs during SSR and client-side
useHead({
  link: [
    { rel: 'alternate', hreflang: 'en', href: 'https://example.com/en' }
  ]
})
</script>
```

`useHead()` called in a component's setup function runs during SSR by default, so this is a risk if you defer the call into a lifecycle hook or a `.client.vue` component.

## Implementation Methods

You can implement hreflang using HTML `<link>` tags, HTTP headers, or XML sitemaps. Best practice is to choose one method and stick to it. Mixing methods creates conflicting signals.

### HTML Link Tags (Recommended)

Most flexible approach. Set per-page using `useHead()`:

```vue
<script setup lang="ts">
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

Useful for non-HTML resources (PDFs, etc.). Configure in server routes:

```ts
// server/routes/document.pdf.get.ts
export default defineEventHandler((event) => {
  setHeader(event, '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"'
  ].join(', '))

  return sendStream(event, createReadStream('/path/to/document.pdf'))
})
```

### XML Sitemap

The [@nuxtjs/sitemap](https://nuxtseo.com/sitemap) module automatically generates hreflang annotations in sitemaps when used with @nuxtjs/i18n:

```ts
// nuxt.config.ts
export default defineNuxtConfig({
  modules: ['@nuxtjs/sitemap', '@nuxtjs/i18n'],

  sitemap: {
    // Sitemap automatically includes hreflang from i18n config
  }
})
```

Generates:

```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
<!-- ❌ Don't point canonical between languages -->
<!-- French page -->
<link rel="canonical" href="https://example.com/en" />
<link rel="alternate" hreflang="fr" href="https://example.com/fr" />
<!-- Conflicting signals: canonical says "I'm a duplicate of EN"
     but hreflang says "I'm the French version" -->
```

## Testing Hreflang

### Manual Inspection

View page source and verify:

- All alternate links are present
- Self-referential link exists
- x-default is set
- All URLs return 200
- All URLs are canonical (not redirects)

### Google Search Console

After implementing hreflang, check [Google Search Console](https://search.google.com/search-console) > International Targeting for errors:

- Missing return tags
- Incorrect language codes
- Invalid URLs

### Third-Party Tools

- [Hreflang Tags Testing Tool](https://hreflang.org)
- [Merkle Hreflang Checker](https://technicalseo.com/tools/hreflang/)
- Search Console reports

## Full Example: Multilingual Nuxt App

Complete implementation with @nuxtjs/i18n:

```ts
// nuxt.config.ts
export default defineNuxtConfig({
  modules: ['@nuxtjs/i18n'],

  i18n: {
    locales: [
      { code: 'en', iso: 'en-US', file: 'en.json' },
      { code: 'fr', iso: 'fr-FR', file: 'fr.json' },
      { code: 'de', iso: 'de-DE', file: 'de.json' },
      { code: 'es', iso: 'es-ES', file: 'es.json' }
    ],
    defaultLocale: 'en',
    strategy: 'prefix',
    langDir: 'locales',
    baseUrl: 'https://example.com',

    detectBrowserLanguage: {
      useCookie: true,
      cookieKey: 'i18n_redirected',
      redirectOn: 'root'
    }
  }
})
```

```json
// locales/en.json
{
  "home": {
    "title": "Welcome",
    "description": "Welcome to our site"
  }
}
```

```json
// locales/fr.json
{
  "home": {
    "title": "Bienvenue",
    "description": "Bienvenue sur notre site"
  }
}
```

```vue
<!-- pages/index.vue -->
<script setup lang="ts">
const { t } = useI18n()

useSeoMeta({
  title: t('home.title'),
  description: t('home.description')
})
</script>

<template>
  <main>
    <h1>{{ $t('home.title') }}</h1>
    <p>{{ $t('home.description') }}</p>
  </main>
</template>
```

## Checklist

<checklist id="nuxt-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
- `x-default` points to a sensible fallback for unmatched languages
- Hreflang URLs return 200, are indexable, and aren't blocked by robots.txt
- Canonical tags point to each page itself, never across language versions
- Hreflang renders in the initial server response, not from `onMounted()`

</checklist>

## Localized Sitemaps

The Nuxt Sitemap module emits hreflang alternates for every locale variant of a URL, so your `/en`, `/fr`, `/de`, and `/es` pages cross-reference correctly in the sitemap as well as the head:

<module-card className="w-1/2" slug="sitemap">



</module-card>
