# How VitePress Handles Multi-Language SEO with hreflang Tags and Dynamic Locale Configuration

> Learn how VitePress masterfully handles multi-language SEO. Discover dynamic locale configuration, hreflang tags, and canonical URLs for optimal search engine visibility.

- Repository: [Datawhale/easy-vibe](https://github.com/datawhalechina/easy-vibe)
- Tags: how-to-guide
- Published: 2026-05-10

---

**VitePress generates comprehensive multi-language SEO metadata by defining a centralized `localeMap` in `docs/.vitepress/config.mjs`, dynamically building hreflang alternate links via the `getSeoHead` function, and synchronizing runtime locale detection through the `useI18n` composable, ensuring every page emits proper canonical URLs, Open Graph tags, and cross-language references for search engines.**

The datawhalechina/easy-vibe documentation site demonstrates a production-grade approach to international SEO in static site generation. By combining compile-time configuration with dynamic head generation, the implementation ensures each localized page—including deeply nested routes—publishes correct `hreflang` annotations, canonical URLs, and social media metadata.

## The Three-Pillar SEO Architecture

The multi-language SEO implementation rests on three coordinated components defined in `docs/.vitepress/config.mjs`: a static locale registry, a dynamic head generator, and a runtime locale binding system.

### Centralized Locale Metadata

At lines 31–92 of `config.mjs`, the `localeMap` object serves as the single source of truth for all language-specific SEO attributes. This plain object maps locale keys (e.g., `zh-cn`, `en`, `ja-jp`) to configurations containing Open Graph locales, Twitter handles, HTML language attributes, and hreflang identifiers.

```js
const localeMap = {
  'zh-cn': { ogLocale: 'zh_CN', twitterSite: '@datawhale', lang: 'zh-CN', hreflang: 'zh-CN' },
  en:     { ogLocale: 'en_US', twitterSite: '@datawhale', lang: 'en-US', hreflang: 'en' },
  // additional locales...
}

```

### Dynamic Head Generation

The `getSeoHead` function (around lines 95–82 in `config.mjs`) constructs the complete `<head>` element for every page. It accepts a locale key, page title, description, and path, then returns a VitePress-compatible head array containing canonical links, Open Graph tags, Twitter Cards, and crucially, the full set of `hreflang` alternate links.

```js
const getSeoHead = (locale, title, description, path = '') => {
  const seoConfig = localeMap[locale] || localeMap['zh-cn']
  const canonicalUrl = path ? `${siteUrl}${path}` : `${siteUrl}/${locale}/`
  const relativePath = getRelativePath(path, locale)   // strips leading "/en/"

  const head = [
    ['link', { rel: 'canonical', href: canonicalUrl }],
    // Open Graph and Twitter tags omitted for brevity...
  ]

  // Generate hreflang alternates for all languages
  Object.keys(localeMap).forEach(lang => {
    let alternateUrl = `${siteUrl}/${lang}/`
    if (relativePath) {
      alternateUrl = `${siteUrl}/${lang}/${relativePath}`
    }
    head.push(['link', { rel: 'alternate', hreflang: localeMap[lang].hreflang, href: alternateUrl }])
  })
  
  // x-default points to Chinese version
  head.push(['link', { rel: 'alternate', hreflang: 'x-default', href: `${siteUrl}/zh-cn/` }])

  return head
}

```

### Runtime Locale Synchronization

To ensure UI components and SEO metadata reference the same language code, [`docs/.vitepress/theme/composables/useI18n.js`](https://github.com/datawhalechina/easy-vibe/blob/main/docs/.vitepress/theme/composables/useI18n.js) (lines 22–30) exposes the active locale through a reactive composable. This pulls from `localeMap` to compute `locale` and provides a translation helper `t`, keeping navigation, breadcrumbs, and page content synchronized with the SEO head.

```js
import { useI18n } from '../composables/useI18n'

export default {
  setup() {
    const { t, locale } = useI18n()
    return { t, locale }
  },
  template: `<h1>{{ t('site.title') }}</h1>`
}

```

## How hreflang Tags Are Generated

VitePress does not automatically create hreflang tags for multi-language sites. The easy-vibe implementation handles this by computing alternate URLs for every page during the build process.

### Handling Deep Page Paths

For nested routes like `/en/stage-2/frontend/figma-mastergo/`, the system cannot simply swap the locale prefix. The `getRelativePath` utility strips the current locale segment from the path, then the generator loops through `Object.keys(localeMap)` to reconstruct the full URL for each language variant:

- Original: `/en/stage-2/frontend/figma-mastergo/`
- Relative path: `stage-2/frontend/figma-mastergo/`
- Chinese alternate: `/zh-cn/stage-2/frontend/figma-mastergo/`
- Japanese alternate: `/ja-jp/stage-2/frontend/figma-mastergo/`

### The x-default Fallback

Following Google’s guidelines for multilingual sites, the implementation adds an `x-default` hreflang tag pointing to the Chinese (`zh-cn`) version at lines configured within `getSeoHead`. This signals to search engines which version to display when no other language matches the user's browser preferences.

## Integrating with VitePress Configuration

At lines 47–66 of `config.mjs`, each locale registers in the `locales` field of the VitePress configuration object. Each entry calls `getSeoHead` with its specific parameters, injecting the generated head array into `themeConfig.head`:

```js
locales: {
  'en': {
    label: 'English',
    lang: 'en-US',
    link: '/en/',
    title: 'Easy-Vibe Tutorial',
    description: 'Learn Vibe Coding with AI from scratch.',
    head: getSeoHead('en', 'Easy-Vibe Tutorial', 'Learn Vibe Coding...', '/en/'),
    themeConfig: { /* ... */ }
  },
  // other locales...
}

```

During static site generation, VitePress merges these head arrays into the final HTML, ensuring every page contains the complete set of SEO metadata.

## Extending to New Languages

Adding support for an additional language requires three steps:

1. **Extend `localeMap`** with the new locale’s SEO attributes:

```js
'pt-br': {
  ogLocale: 'pt_BR',
  twitterSite: '@datawhale',
  lang: 'pt-BR',
  hreflang: 'pt-BR'
}

```

2. **Register the locale** in the `locales` configuration block, calling `getSeoHead` with the new locale key, title, and description.

3. **Create the content directory** at `docs/pt-br/` mirroring the structure of existing locales. The hreflang generation logic automatically includes the new language in the alternate link loop without further configuration changes.

## Summary

- **Centralized configuration**: The `localeMap` object in `config.mjs` defines all SEO-relevant locale metadata in one location.
- **Dynamic generation**: The `getSeoHead` function builds canonical URLs, Open Graph tags, and a complete set of hreflang alternate links for every page, including deep paths.
- **Path manipulation**: By stripping locale prefixes and rebuilding URLs, the system ensures cross-language links point to equivalent content across all languages.
- **Runtime sync**: The `useI18n` composable binds the runtime locale to the SEO configuration, preventing mismatches between page content and metadata.
- **x-default support**: The implementation includes the recommended `x-default` hreflang tag pointing to the primary Chinese language version.

## Frequently Asked Questions

### How does VitePress know which hreflang tags to generate for a specific page?

VitePress relies on the custom `getSeoHead` function defined in `docs/.vitepress/config.mjs`. This function receives the current page path, strips the locale prefix using `getRelativePath`, then iterates over all keys in `localeMap` to construct alternate URLs. It emits a `<link rel="alternate" hreflang="...">` tag for every supported language plus an `x-default` entry, ensuring search engines discover all language variants regardless of which version they crawl initially.

### What happens if I add a page in one language but not another?

The hreflang tags are generated based on the locale configuration and path structure, not content availability. If you create `/en/new-page/` but lack a corresponding `/zh-cn/new-page/`, the hreflang link for the Chinese version will still be generated (pointing to `/zh-cn/new-page/`), resulting in a 404 unless you create the content. To maintain SEO integrity, ensure content parity across locales or implement conditional logic in `getSeoHead` to check file existence before emitting alternate links.

### Why is the x-default hreflang set to zh-cn instead of auto-detecting the user's language?

According to the implementation in `config.mjs`, the `x-default` tag hardcodes the URL to the Chinese (`zh-cn`) version using `href: ${siteUrl}/zh-cn/`. This follows the pattern where `x-default` should point to a language selector page or the primary market version. In the easy-vibe setup, Chinese serves as the primary content language, making it the appropriate fallback when a user's browser preferences do not match any of the specific declared languages.

### How do I verify that hreflang tags are rendered correctly on my VitePress site?

Build the site using `vitepress build` and inspect the generated HTML in the `dist` directory (or your configured output directory). Look for `<link rel="alternate">` tags in the `<head>` section of any page. Each should contain `hreflang` attributes matching your defined locales, with URLs correctly reflecting the page's path structure. You can also use SEO auditing tools or Google Search Console to validate the hreflang implementation across your domain.