Guide

Custom Route Paths

Customize the names of the paths for specific locale.

In some cases, you might want to translate URLs in addition to having them prefixed with the locale code. There are two methods of configuring custom paths, through Module configuration or from within each Page component.

Which method is used is configured by setting the customRoutes options this is set to 'page' by default. Using both methods at the same time is not possible.

Custom paths are not supported when using the 'no_prefix'strategy unless combined with differentDomains.

Module configuration

Make sure you set the customRoutes option to 'config' and add your custom paths in the pages option:

nuxt.config.ts
export default defineNuxtConfig({
  i18n: {
    customRoutes: 'config', // disable custom route with page components
    pages: {
      about: {
        en: '/about-us', // -> accessible at /about-us (no prefix since it's the default locale)
        fr: '/a-propos', // -> accessible at /fr/a-propos
        es: '/sobre' // -> accessible at /es/sobre
      }
    }
  }
})

Note that each key within the pages object should correspond to the route name of the route to localize.

Customized route paths must start with a / and must not include the locale prefix.

You can now use the localePath() function or the <NuxtLinkLocale> component but be sure to use named routes. For example route '/services/advanced' should be 'services-advanced':

<script setup>
const { t } = useI18n()
</script>

<template>
  <NuxtLinkLocale to="about"> {{ t('about') }} </NuxtLinkLocale>
  <NuxtLinkLocale to="services-advanced"> {{ t('advanced') }} </NuxtLinkLocale>
</template>

Or:

<script setup>
const { t } = useI18n()
const localePath = useLocalePath()
</script>

<template>
  <NuxtLink :to="localePath('about')"> {{ t('about') }} </NuxtLink>
  <NuxtLink :to="localePath('services-advanced')"> {{ t('advanced') }} </NuxtLink>
</template>
Passing a path to localePath() is currently not supported.

Example 1: Basic URL localization

You have some routes with the following pages directory:

Directory structure
-| pages/
---| me.vue
---| about.vue
---| services/
-----| index.vue
-----| advanced.vue

You would need to set up your pages property as follows:

nuxt.config.ts
export default defineNuxtConfig({
  i18n: {
    customRoutes: 'config',
    pages: {
      me: {
        fr: '/moi',
      },
      about: {
        fr: '/a-propos',
      },
      services: {
        fr: '/offres',
      },
      'services-advanced': {
        fr: '/offres/avancee',
      }
    }
  }
})
All URLs must start with /

Example 2: Localize the part of URL

You have some routes with the following pages directory:

Directory structure
-| pages/
---| about.vue
---| services/
-----| index.vue
-----| coaching.vue
-----| development/
-------| app.vue
-------| index.vue
-------| website.vue

You would need to set up your pages property as follows:

nuxt.config.ts
export default defineNuxtConfig({
  i18n: {
    customRoutes: 'config',
    pages: {
      about: {
        fr: '/a-propos'
      },
      services: {
        fr: '/offres'
      },
      'services-development': {
        fr: '/offres/developement'
      },
      'services-development-app': {
        fr: '/offres/developement/app'
      },
      'services-development-website': {
        fr: '/offres/developement/site-web'
      },
      'services-coaching': {
        fr: '/offres/formation'
      }
    }
  }
})

If a custom path is missing for one of the locales, the defaultLocale custom path is used, if set.

Example 3: Dynamic Routes

Say you have some dynamic routes like:

Directory structure
-| pages/
---| blog/
-----| [date]/
-------| [slug].vue

Here's how you would configure these particular pages in the configuration:

nuxt.config.ts
export default defineNuxtConfig({
  i18n: {
    customRoutes: 'config',
    pages: {
      'blog-date-slug': {
        // params need to be put back here as you would with Nuxt Dynamic Routes
        // https://nuxt.com/docs/guide/directory-structure/pages#dynamic-routes
        ja: '/blog/tech/[date]/[slug]'
        // ...
      }
    }
  }
})

Page component

Note for those updating to v8.0.1 or higher

Path parameters parsing has been changed to match that of Nuxt 3, you will have to update your custom paths (e.g. '/example/:param' should now be '/example/[param]')

You can use the defineI18nRoute() compiler macro to set custom paths for each page component.

pages/about.vue
<script setup>
  defineI18nRoute({
    paths: {
      en: '/about-us', // -> accessible at /about-us (no prefix since it's the default locale)
      fr: '/a-propos', // -> accessible at /fr/a-propos
      es: '/sobre' // -> accessible at /es/sobre
    }
  })
</script>

To configure a custom path for a dynamic route, you need to use it in double square brackets in the paths similarly to how you would do it in Nuxt Dynamic Routes:

pages/articles/[name].vue
<script setup>
  defineI18nRoute({
    paths: {
      en: '/articles/[name]',
      es: '/artículo/[name]'
    }
  })
</script>
defineI18nRoute() compiler macro is tree-shaken out at build time and is not included in the dist files.

Dynamic route parameters

Dealing with dynamic route parameters requires a bit more work because you need to provide parameters translations to Nuxt i18n module. The composable useSetI18nParams can be used to set translations for route parameters, this is used to set SEO tags as well as changing the routes returned by switchLocalePath.

During SSR it matters when and where you set i18n parameters, since there is no reactivity during SSR.

Components which have already been rendered do not update by changes introduced by pages and components further down the tree. Instead, these links are updated on the client side during hydration, in most cases this should not cause issues.

This is not the case for SEO tags, these are updated correctly during SSR regardless of when and where i18n parameters are set.

Check out the experimental switchLocalePathLinkSSR feature, which combined with the <SwitchLocalePathLink> component, correctly renders links during SSR regardless of where and when it is used.

An example (replace slug with the applicable route parameter):

<script setup>
// fetch product from API... (red mug)

const setI18nParams = useSetI18nParams()
setI18nParams({
  en: { slug: data.slugs.en }, // slug: 'red-mug'
  nl: { slug: data.slugs.nl } // slug: 'rode-mok'
})

const switchLocalePath = useSwitchLocalePath()
switchLocalePath('en') // /products/red-mug
switchLocalePath('nl') // /nl/products/rode-mok
</script>

<template>
  <!-- pages/products/[slug].vue -->
</template>

Note that for the special case of a catch-all route named like [...pathMatch].vue, the key of the object needs to say pathMatch. For example:

<script>
const setI18nParams = useSetI18nParams()
setI18nParams({
  en: { pathMatch: ['not-found-my-post'] },
  fr: { pathMatch: ['not-found-mon-article'] }
})
</script>

<template>
  <!-- pages/[...pathMatch].vue -->
</template>

Note that a catch all route is defined as an array. In this case, there is only one element, but if you want to use a sub-path, for example '/not-found/post', define multiple elements as in ['not-found', 'post']. You will need to define more than one, e.g. ['not-found', 'post'].

Nuxt i18n module won't reset parameters translations for you, this means that if you use identical parameters for different routes, navigating between those routes might result in conflicting parameters. Make sure you always set params translations in such cases.

definePageMeta({ name: '...' }) caveat

By default Nuxt overwrites generated route values at build time which breaks custom named routes (setting name with definePageMeta()) when resolving localized paths.

Nuxt v3.10 introduced the experimental feature scanPageMeta, this needs to be enabled for custom named routes to work when using Nuxt I18n.

This experimental feature can be enabled as shown here:

nuxt.config.ts
export default defineNuxtConfig({
  experimental: {
    scanPageMeta: true,
  }
})

Copyright © 2024