Nuxt i18n
Guide

New features

What's new in v10

Custom routes via definePageMeta()

We have added support for setting custom routes for pages using the definePageMeta() API, which is now the recommended way to set custom routes for pages. This method is used in all customRoutes modes and takes precedence over custom routes set with the pages option or the defineI18nRoute() macro.

Since v10.5 custom paths set with definePageMeta() are used in all customRoutes modes:

  • You no longer need to switch to customRoutes: 'meta' to define custom paths in page components.
  • Migration is incremental — move custom paths from the pages option or the macro into page components one route at a time; the definePageMeta() value takes precedence and a warning is logged during build if a route has paths from multiple sources.
  • Route meta is the canonical source of custom paths at build time, so projects and modules can set custom paths on routes they add or mutate in the pages:extend hook, see Hooks and modules.

To migrate from the defineI18nRoute() macro, you can simply replace it with definePageMeta() and set the i18n property with the same options:

pages/about.vue
<script setup>
definePageMeta({
  i18n: {
    paths: {
      en: '/about-us',
      fr: '/a-propos',
    }
  }
})
</script>

Nitro-side language detection and redirection

Language detection and redirection has been reimplemented to be handled from the Nitro server, this allows us to redirect requests earlier in the request lifecycle which improves performance.

The previous implementation did not work correctly when combined with prerendering which this new implementation does.

While this change makes detection and redirection more accurate and should better match the documented behavior, if this causes issues in your project it can be disabled by setting experimental.nitroContextDetection: false in the module options. The option to disable this feature is temporary and will be removed in a future version.

Experimental strict SEO mode

We have added a new experimental option strictSeo that enables strict SEO mode, which changes the way i18n head tags are handled.

With strict SEO mode enabled, the i18n head tags are managed internally, this allows for some much requested improvements:

  • The module will no longer add alternate tags for unsupported locales when setting localized dynamic route params.
  • Unsupported locale links used with <SwitchLocalePathLink> are disabled, their links will be set to '#' and will have a data-i18n-disabled attribute for styling purposes.
  • The useLocaleHead() is no longer needed in strict SEO mode, i18n tags are automatically set by the module and usage will throw an error.
  • Canonical query parameters are configured globally with experimental.strictSeo.canonicalQueryParams.
  • The useSetI18nParams() inherits the global canonical query parameter config which can be overridden through its options parameter.

If this mode proves stable it will become the default in v11, please try it out and report any issues you encounter.

Compact routes

We have added a new experimental option compactRoutes that changes how locale-prefixed routes are generated. Instead of creating a separate route for every locale, eligible routes are compacted into a single route using a regex parameter for the locale segment.

For example, with three locales (en, fr, ja) and the prefix strategy, the /about page would normally produce three routes:

/en/about
/fr/about
/ja/about

With compactRoutes enabled, these are compacted into a single route:

/:locale(en|fr|ja)/about

This can be enabled in nuxt.config.ts:

nuxt.config.ts
export default defineNuxtConfig({
  i18n: {
    experimental: {
      compactRoutes: true
    }
  }
})

Why this is an improvement:

  • Fewer routes — Applications with many pages and locales can generate a large number of routes (pages × locales). Compact routes significantly reduces this, resulting in a smaller route table and faster route matching.
  • Smaller bundle size — The router configuration shipped to the client is more compact, which reduces the JavaScript payload.
  • Better scalability — The number of routes stays proportional to the number of pages rather than growing multiplicatively with the number of locales.

How it works with different strategies:

  • prefix — All locales are compacted into a single route.
  • prefix_except_default — The default locale keeps its unprefixed route, while all other locales are compacted into a single route.
  • prefix_and_default — The default locale keeps its unprefixed route, and all locales (including the default) are compacted into a single route.

When routes cannot be compacted:

Not all routes are eligible for compaction. A route will still be generated per locale when:

  • It has custom per-locale paths defined (e.g., /about in English but /a-propos in French).
  • It is not available for all configured locales (e.g., a route is disabled for certain locales).

In these cases, the module automatically falls back to the standard per-locale route generation for that specific route, while still compacting all eligible routes.

This option is opt-in for now but is expected to become the default in v11. Please try it out and report any issues you encounter.

Optimized message bundling

We have changed how locale messages reach server builds. Static locale files (JSON/JSON5/YAML) used to be handed to the bundler, which expands them into modules and generates source maps for them. Since the server compiles messages at runtime either way, this work only slowed the build down. These files are now embedded as raw messages instead, and served to the runtime as Nitro server assets.

This is controlled by optimizeMessageBundling, which is enabled by default and can be turned off in nuxt.config.ts:

nuxt.config.ts
export default defineNuxtConfig({
  i18n: {
    experimental: {
      optimizeMessageBundling: false
    }
  }
})

Why this is an improvement:

  • Faster builds — The bundler no longer parses and generates source maps for the entire message data in server builds, in our benchmarks this cut build time by more than half for projects with large locale files.
  • Lower memory usage — Peak build memory scales with message volume, projects with very large locale files could previously run out of memory during the server build.
  • Smaller server output — Message data is no longer expanded into module declarations, reducing the size of the server bundle.

The client bundle is unaffected, and messages are compiled at runtime on the server as they were before, so there is no behavioral change for rendered messages.

When to turn it off:

  • A custom Nitro Rollup plugin transforms your locale files — it no longer sees them, since they are not imported into the server graph at all.

A locale file the module cannot parse is left to the bundler instead of failing the build, so files your bundler accepts keep working — they just miss out on the optimization.

New in v10.6.0. Set optimizeMessageBundling: false if you hit a problem, and please report it.
Static locale files are read and parsed by the module rather than the bundler. If a locale file contains HTML, it is reported as a build warning — see compilation.strictMessage.
Copyright © 2026