Skip to content

fix(nextjs): Parameterize [lng] locale routes and prefer narrower routes over catch-alls - #23520

Merged
chargome merged 3 commits into
developfrom
fix/nextjs-lng-locale-route-parameterization
Aug 25, 2026
Merged

fix(nextjs): Parameterize [lng] locale routes and prefer narrower routes over catch-alls#23520
chargome merged 3 commits into
developfrom
fix/nextjs-lng-locale-route-parameterization

Conversation

@chargome

@chargome chargome commented Aug 24, 2026

Copy link
Copy Markdown
Member

[lng] locale routes were reported as the 404 catch-all. Two causes:

  • hasOptionalPrefix only matched locale/lang/language, so [lng] never got the unprefixed-default-locale retry.
  • Route specificity summed per-segment scores, letting a short catch-all (/:lng/:notFound*) beat a longer but strictly narrower route (/:lng/guides/:category/:rest*). It is now compared segment by segment. This one also affected prefixed locales, which the issue assumed were working.

Also adds routeManifestInjection.localeParamNames to override the built-in list, as raised in #17927. It replaces rather than extends the defaults so an app with a genuine non-i18n [lang] param can opt out.

Fixes #23488

…outes over catch-alls

Routes using a `[lng]` locale param were reported as the 404 catch-all. Two causes:

`hasOptionalPrefix` only matched `locale`, `lang` and `language`, so `[lng]` never got the
unprefixed-default-locale retry.

Route specificity summed per-segment scores, letting a short catch-all beat a longer but
strictly narrower route. Compare segment by segment instead. This also affected prefixed
locales, not just unprefixed ones.

Add `routeManifestInjection.localeParamNames` to override the built-in list. It replaces
rather than extends the defaults so apps with a non-i18n `[lang]` param can opt out.

Fixes #23488
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@chargome chargome self-assigned this Aug 24, 2026
@chargome

Copy link
Copy Markdown
Member Author

bugbot run

Comment thread packages/nextjs/src/client/routing/parameterization.ts Outdated
@github-actions

github-actions Bot commented Aug 24, 2026

Copy link
Copy Markdown
Contributor

size-limit report 📦

⚠️ Warning: Base artifact is not the latest one, because the latest workflow run is not done yet. This may lead to incorrect results. Try to re-run all tests to get up to date results.

Path Size % Change Change
@sentry/browser 28.57 kB - -
@sentry/browser - with treeshaking flags 26.92 kB - -
@sentry/browser - with treeshaking flags tracing without tracing 26.82 kB - -
@sentry/browser (incl. Tracing) 48.33 kB - -
@sentry/browser (incl. Tracing + Span Streaming) 48.35 kB - -
@sentry/browser (incl. Tracing, Profiling) 51.24 kB - -
@sentry/browser (incl. Tracing, Replay) 87.73 kB - -
@sentry/browser (incl. Tracing, Replay) - with treeshaking flags 77.19 kB - -
@sentry/browser (incl. Tracing, Replay with Canvas) 92.44 kB - -
@sentry/browser (incl. Tracing, Replay, Feedback) 105.12 kB - -
@sentry/browser (incl. Feedback) 45.81 kB - -
@sentry/browser (incl. sendFeedback) 33.36 kB - -
@sentry/browser (incl. FeedbackAsync) 38.47 kB - -
@sentry/browser (incl. Metrics) 29.52 kB - -
@sentry/browser (incl. Logs) 29.8 kB - -
@sentry/browser (incl. Metrics & Logs) 30.45 kB - -
@sentry/react 30.33 kB - -
@sentry/react (incl. Tracing) 50.51 kB - -
@sentry/vue 35.4 kB - -
@sentry/vue (incl. Tracing) 50.31 kB - -
@sentry/svelte 28.6 kB - -
CDN Bundle 30.32 kB - -
CDN Bundle (incl. Tracing) 48.87 kB - -
CDN Bundle (incl. Logs, Metrics) 32.54 kB - -
CDN Bundle (incl. Tracing, Logs, Metrics) 50.74 kB - -
CDN Bundle (incl. Replay, Logs, Metrics) 72.91 kB - -
CDN Bundle (incl. Tracing, Replay) 86.33 kB - -
CDN Bundle (incl. Tracing, Replay, Logs, Metrics) 88.16 kB - -
CDN Bundle (incl. Tracing, Replay, Feedback) 92.07 kB - -
CDN Bundle (incl. Tracing, Replay, Feedback, Logs, Metrics) 94 kB - -
CDN Bundle - uncompressed 89.94 kB - -
CDN Bundle (incl. Tracing) - uncompressed 146.13 kB - -
CDN Bundle (incl. Logs, Metrics) - uncompressed 96.23 kB - -
CDN Bundle (incl. Tracing, Logs, Metrics) - uncompressed 151.81 kB - -
CDN Bundle (incl. Replay, Logs, Metrics) - uncompressed 225.18 kB - -
CDN Bundle (incl. Tracing, Replay) - uncompressed 265.4 kB - -
CDN Bundle (incl. Tracing, Replay, Logs, Metrics) - uncompressed 271.07 kB - -
CDN Bundle (incl. Tracing, Replay, Feedback) - uncompressed 279.09 kB - -
CDN Bundle (incl. Tracing, Replay, Feedback, Logs, Metrics) - uncompressed 284.76 kB - -
@sentry/nextjs (client) 53.06 kB +0.03% +11 B 🔺
@sentry/sveltekit (client) 48.74 kB - -
@sentry/core/server 64.95 kB - -
@sentry/core/browser 52.11 kB - -
@sentry/node 117.49 kB +0.03% +24 B 🔺
@sentry/node/import (ESM hook with diagnostics-channel injection) 85.18 kB - -
@sentry/node - without tracing 82.03 kB +0.03% +24 B 🔺
@sentry/aws-serverless 91.46 kB +0.03% +20 B 🔺
@sentry/cloudflare (withSentry) - minified 194.68 kB - -
@sentry/cloudflare (withSentry) 481.33 kB - -

View base workflow run

The length tiebreaker preferred the longer route whenever the shared segments tied, so '/fr'
matched '/:lng/:notFound*' via the optional-prefix retry instead of '/:lng'.

Compare one segment past the shorter route instead, ranking the end of a route between a
dynamic segment and a catch-all: continuing into a static or dynamic segment narrows a route,
continuing into a catch-all widens it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@chargome

Copy link
Copy Markdown
Member Author

bugbot run

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Bugbot reviewed your changes and found no new issues!

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit ddbdd91. Configure here.

@chargome
chargome marked this pull request as ready for review August 24, 2026 13:23
@chargome
chargome requested a review from a team as a code owner August 24, 2026 13:23
@chargome
chargome requested review from logaretm, mydea and nicohrubec and removed request for a team and mydea August 24, 2026 13:23
@chargome
chargome merged commit 24060f2 into develop Aug 25, 2026
78 of 79 checks passed
@chargome
chargome deleted the fix/nextjs-lng-locale-route-parameterization branch August 25, 2026 07:33
JPeer264 pushed a commit that referenced this pull request Aug 25, 2026
…outes over catch-alls (#23520)

`[lng]` locale routes were reported as the 404 catch-all. Two causes:

- `hasOptionalPrefix` only matched `locale`/`lang`/`language`, so
`[lng]` never got the unprefixed-default-locale retry.
- Route specificity summed per-segment scores, letting a short catch-all
(`/:lng/:notFound*`) beat a longer but strictly narrower route
(`/:lng/guides/:category/:rest*`). It is now compared segment by
segment. This one also affected prefixed locales, which the issue
assumed were working.

Also adds `routeManifestInjection.localeParamNames` to override the
built-in list, as raised in #17927. It replaces rather than extends the
defaults so an app with a genuine non-i18n `[lang]` param can opt out.

Fixes #23488

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Client route parameterization names everything /:lng/:notFound* when the locale param is [lng] and the default locale has no URL prefix

2 participants