Replies: 1 comment 1 reply
|
Would disabling i18n routing for those routes through Either like this: <script setup>
definePageMeta({ i18n: false })
</script>Or through the hook export default defineNuxtConfig({
hooks: {
'pages:extend'(pages) {
for (const page of pages) {
if (/* match dashboard pages */) {
page.meta ??= {}
page.meta.i18n = false
}
}
}
}
}) |
1 reply
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Allow
i18n.strategyto be defined per-route (via glob patterns), instead of a single global value, so different parts of an app can use different URL/locale routing behavior. Configuration is modeled after Nuxt's[routeRules](https://nuxt.com/docs/4.x/guide/concepts/rendering#hybrid-rendering).Motivation
Today,
strategyis a single global setting applied to every route:This doesn't fit apps that have both public, SEO-sensitive pages and authenticated/connected areas:
prefix_except_defaultis the right strategy here: each non-default locale gets its own crawlable, indexable URL (/fr/about), while the default locale stays unprefixed (/about).no_prefixwith automatic locale detection (browser/cookie/user preference) gives a cleaner UX with no impact on SEO, since these routes shouldn't be indexed at all.Because
strategyis global today, you're forced into a single trade-off across the whole app. There is no way to say "SEO-correct prefixing on public routes, prefix-free auto-detected locale on the dashboard" in one config.Proposal
Widen the type of
i18n.strategyitself, rather than introducing new option names.strategyaccepts either:[routeRules](https://nuxt.com/docs/4.x/guide/concepts/rendering#hybrid-rendering).There is no separate "default" key. Instead, rule ordering/specificity is what determines the outcome, mirroring how
routeRulesitself resolves overlapping patterns: a broad catch-all pattern (/**) establishes the baseline, and more specific patterns declared for a subset of routes override it for the routes they match. This keeps the mental model identical torouteRules— no new concepts for readers already familiar with it to learn.Rules:
routeRules), each value is one of the existing strategy identifiers:no_prefix,prefix,prefix_except_default,prefix_and_default./**acts as the catch-all/default entry — every route matches it, so it's the fallback for anything not covered by a more specific pattern.routeRules. Declaring/dashboard/**after/**overrides the catch-all for everything under/dashboard.strategyis an object with no/**(or equivalent catch-all) entry, unmatched routes fall back to the module's built-in default (prefix_except_default), same as today whenstrategyisn't set at all.defaultLocalestays a single global setting, independent ofstrategy. Its role — the canonical/unprefixed locale underprefix_except_default, and the fallback when detection yields nothing underno_prefix— is meaningful the same way no matter which strategy a given route uses, so there's no need for a per-route override.Config example
Route matching semantics
routeRulesfor consistency and to avoid teaching a second pattern language.<link rel="alternate">SEO tags are computed per matched strategy rather than assuming one strategy app-wide.routeRules: more specific/nested patterns override broader ones regardless of declaration order, so/dashboard/**always overrides/**for routes under/dashboard, and/dashboard/public/**would in turn override/dashboard/**if both are defined.routeRulesitself doesn't warn/error on overlapping patterns, it just resolves via the router's matching order, and this proposal mirrors that behavior exactly rather than inventing stricter validation.localePath,switchLocalePath,<NuxtLinkLocale>, etc.) already resolve against a target route name/path. They just need to match the glob rules using that destination route rather than the currently active one. This falls out naturally from the matching logic already described above — no separate mechanism needed. So<NuxtLinkLocale :to="{ name: 'index' }">rendered on ano_prefixdashboard page correctly links to/fr/(matchingindex'sprefix_except_defaultrule), and the in-page locale switcher on that same dashboard page correctly stays prefix-free (matching the current route'sno_prefixrule) — both cases are just "resolve strategy for route X" applied to different values of X.Implementation considerations (by Claude Code)
Checked against the current
@nuxtjs/i18nsource — this is a bigger internal lift than the config change alone suggests, on both the build and runtime sides:src/routing.ts):localizeRoutescurrently takesstrategyonce and applies it to the whole page tree in a single pass. Three things are strategy-driven at that level:shouldLocalizeRoutesdecides whether to localize routes at all — for a single-domainno_prefixapp, it skips localization entirely, since every locale shares the same unprefixed path;createShouldPrefixdecides per route/locale whether to add a locale prefix;resolveDefaultTreeLocales/resolveDefaultLocalescompute which locales additionally get an unprefixed "default tree" (needed forprefix_and_default). All three currently read one globalstrategyvalue for the entire tree. The walk that builds routes (localizeSingleRouteinsrc/kit/gen.ts) does carry the full accumulated path per route already, so per-route glob matching is feasible insidecreateShouldPrefix— butshouldLocalizeRoutes's all-or-nothing skip and the default-tree-locale computation both assume a single strategy for the whole build, and need to become per-route/per-subtree instead of a single upfront decision.strategyis compiled into the client/server bundle as a literal constant (__I18N_STRATEGY__, set fromoptions.strategyinsrc/bundler.ts) and read as a flat value in several places: the path matcher (src/runtime/shared/matching.ts), navigation guards (src/runtime/routing/navigation.ts), the composer's publicstrategyvalue (src/runtime/plugins/i18n.ts), and the server plugin (src/runtime/server/plugin.ts). Supporting per-route strategy means each of these needs to resolve strategy for the current (or target) route instead of reading one baked-in constant — includingcomposer.strategy, which is a value userland code may currently assume is static for the whole session.Backward compatibility
At the config level, this is a type widening of the existing
strategyoption rather than a new option name, so no migration or deprecation is needed there:strategy: 'prefix_except_default'(string) continues to work exactly as today — untouched code path.strategy: { '/**': '...', ... }(object) is purely additive; existing configs are unaffected either way.All reactions