Skip to content

Conversation

logaretm
Copy link
Collaborator

Problem

When using Next.js 15 App Router with next-intl and localePrefix: "as-needed", Web Vitals and transaction names were inconsistent across locales:

  • /foo (default locale, no prefix) → Transaction: /:locale
  • /ar/foo (non-default locale, with prefix) → Transaction: /:locale/foo

This caused all default locale pages to collapse into a single /:locale transaction, making Web Vitals data unusable for apps with i18n routing.

After investigation it seems like the route parameterization logic couldn't match /foo (1 segment) to the /:locale/foo pattern (expects 2 segments) because the locale prefix is omitted in default locale URLs.

Solution

Implemented enhanced route matching with automatic i18n prefix detection:

  1. Route Manifest Metadata - Added hasOptionalPrefix flag to route info to identify routes with common i18n parameter names (locale, lang, language)
  2. Smart Fallback Matching - When a route doesn't match directly, the matcher now tries prepending a placeholder segment for routes flagged with hasOptionalPrefix
    • Example: /foo → tries matching as /PLACEHOLDER/foo → matches /:locale/foo
  3. Updated Specificity Scoring - changed route specificity calculation to prefer longer routes when dynamic segment counts are equal
    • Example: /:locale/foo (2 segments) now beats /:locale (1 segment)

Result

After fix:

URL: /foo         → Transaction: /:locale/foo    ✅
URL: /ar/foo      → Transaction: /:locale/foo    ✅
URL: /products    → Transaction: /:locale/products ✅
URL: /ar/products → Transaction: /:locale/products ✅

All routes now consistently use the same parameterized transaction name regardless of locale, making Web Vitals properly grouped and usable.

Backwards Compatibility

  • No breaking changes - only applies when direct matching would fail
  • Only affects routes with first param named locale/lang/language
  • Non-i18n apps completely unaffected
  • Direct matches always take precedence over optional prefix matching

Fixes #17775


Maybe we should make certain aspects of this configurable, like the ['locale', 'lang', 'language'] collection

@github-actions
Copy link
Contributor

github-actions bot commented Oct 14, 2025

size-limit report 📦

Path Size % Change Change
@sentry/browser 24.64 kB - -
@sentry/browser - with treeshaking flags 23.14 kB - -
@sentry/browser (incl. Tracing) 40.99 kB - -
@sentry/browser (incl. Tracing, Replay) 79.29 kB - -
@sentry/browser (incl. Tracing, Replay) - with treeshaking flags 68.98 kB - -
@sentry/browser (incl. Tracing, Replay with Canvas) 84 kB - -
@sentry/browser (incl. Tracing, Replay, Feedback) 96.16 kB - -
@sentry/browser (incl. Feedback) 41.33 kB - -
@sentry/browser (incl. sendFeedback) 29.3 kB - -
@sentry/browser (incl. FeedbackAsync) 34.26 kB - -
@sentry/react 26.35 kB - -
@sentry/react (incl. Tracing) 42.99 kB - -
@sentry/vue 29.13 kB - -
@sentry/vue (incl. Tracing) 42.79 kB - -
@sentry/svelte 24.66 kB - -
CDN Bundle 26.94 kB - -
CDN Bundle (incl. Tracing) 41.65 kB - -
CDN Bundle (incl. Tracing, Replay) 77.89 kB - -
CDN Bundle (incl. Tracing, Replay, Feedback) 83.35 kB - -
CDN Bundle - uncompressed 78.95 kB - -
CDN Bundle (incl. Tracing) - uncompressed 123.53 kB - -
CDN Bundle (incl. Tracing, Replay) - uncompressed 238.54 kB - -
CDN Bundle (incl. Tracing, Replay, Feedback) - uncompressed 251.31 kB - -
@sentry/nextjs (client) 45.13 kB +0.18% +78 B 🔺
@sentry/sveltekit (client) 41.42 kB - -
@sentry/node-core 50.78 kB - -
@sentry/node 154.4 kB - -
@sentry/node - without tracing 92.65 kB - -
@sentry/aws-serverless 106.35 kB - -

View base workflow run

@logaretm logaretm marked this pull request as ready for review October 14, 2025 22:50
@chargome chargome self-requested a review October 15, 2025 08:08
Copy link
Member

@chargome chargome left a comment

Choose a reason for hiding this comment

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

Sweet! Could we add a e2e test for this e.g. in next-15-basebath?

@logaretm logaretm force-pushed the awad/js-985-nextjs-1552-next-intl-web-vitals-duplicated-across-locale branch from 7e23ca1 to 68b20a8 Compare October 15, 2025 14:55
@logaretm
Copy link
Collaborator Author

I might've overdid it but testing the setup with other tests in place is tricky, so I set up another nextjs project with the actual intl-next package.

@logaretm logaretm force-pushed the awad/js-985-nextjs-1552-next-intl-web-vitals-duplicated-across-locale branch from 68343f8 to 21c72bc Compare October 16, 2025 09:01
@logaretm logaretm force-pushed the awad/js-985-nextjs-1552-next-intl-web-vitals-duplicated-across-locale branch from 21c72bc to 89b04f7 Compare October 17, 2025 11:35
Copy link
Member

@chargome chargome left a comment

Choose a reason for hiding this comment

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

Thanks for adding the test! Since they all run in parallel and this is a fast one I'd not expect this to be a problem.

Great work!

@logaretm logaretm merged commit af83b87 into develop Oct 17, 2025
60 checks passed
@logaretm logaretm deleted the awad/js-985-nextjs-1552-next-intl-web-vitals-duplicated-across-locale branch October 17, 2025 11:59
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.

Next.js 15.5.2 next-intl: Web Vitals duplicated across /:locale and normalized routes despite transaction normalization attempts

2 participants