Is there an existing issue for this?
How do you use Sentry?
Sentry Saas (sentry.io)
Which SDK are you using?
@sentry/nextjs
SDK Version
10.14.0
Framework Version
Next 15.5.2
Link to Sentry event
No response
Reproduction Example/SDK Setup
GitHub Issue: Sentry + Next.js 15 + next-intl Web Vitals Issue
Issue Title: Critical: Next.js 15 + next-intl App Router creates unusable Sentry Web Vitals - all English routes collapse to /:locale, navigation not tracked
Repository: https://github.com/getsentry/sentry-javascript/issues
Description
I'm experiencing a critical issue with Sentry Web Vitals in a Next.js 15 App Router application using next-intl with localePrefix: "as-needed". The transaction naming is completely broken, making performance monitoring unusable.
Environment
- Sentry SDK Version:
@sentry/nextjs@10.14.0
- Next.js Version:
15.5.2
- next-intl Version:
^4.3.7
- Configuration: App Router with
app/[locale]/ structure
Critical Issues
1. All English routes collapse to /:locale
When browsing in English (default locale, no URL prefix):
- Visiting
/ creates transaction: /:locale
- Visiting
/foo creates transaction: /:locale
- Visiting
/bar creates transaction: /:locale
- All different pages show as the same route in Sentry
2. Arabic routes get parameterized patterns
When browsing in Arabic (with /ar prefix):
- Visiting
/ar/foo creates transaction: /:locale/foo
- Should be normalized to
/foo for proper grouping
3. Duplicate transactions for root page
- Visiting
/ (English) sometimes creates BOTH:
- Transaction:
/ (correct)
- Transaction:
/:locale (incorrect duplicate)
4. Next.js navigation completely broken
- Locale switching (e.g., from
/ to /ar) doesn't create Web Vitals transactions
<Link> navigation between pages doesn't trigger Web Vitals
- Only direct page loads/refreshes create performance data
Current Configuration
next.config.ts
import {withSentryConfig} from "@sentry/nextjs";
import { NextConfig } from 'next';
import createNextIntlPlugin from 'next-intl/plugin';
const nextConfig: NextConfig = {
output: "standalone",
transpilePackages: ["@t3-oss/env-nextjs", "@t3-oss/env-core"],
htmlLimitedBots: /.*/,
productionBrowserSourceMaps: true,
};
const withNextIntl = createNextIntlPlugin({
requestConfig: './i18n/request.ts',
experimental: {
createMessagesDeclaration: ["./messages/en.json", "./messages/ar.json"],
},
}
);
export default withSentryConfig(withNextIntl(nextConfig), {
org: "fyler",
project: "javascript-nextjs-5v",
authToken: process.env.SENTRY_AUTH_TOKEN,
silent: !process.env.CI,
widenClientFileUpload: true,
disableLogger: true,
release: { setCommits: { auto: true } },
sourcemaps: {
disable: false,
assets: [
".next/static/**/*.js",
".next/static/**/*.js.map",
".next/server/**/*.js",
".next/server/**/*.js.map",
".next/edge-runtime-webpack.js",
".next/edge-runtime-webpack.js.map",
".next/instrumentation.js",
".next/instrumentation.js.map",
".next/middleware.js",
".next/middleware.js.map"
],
ignore: ["**/node_modules/**"],
deleteSourcemapsAfterUpload: false,
},
});
instrumentation-client.ts
import * as Sentry from "@sentry/nextjs";
Sentry.init({
dsn: process.env.NEXT_PUBLIC_SENTRY_DSN,
environment: process.env.NODE_ENV,
integrations: [Sentry.replayIntegration()],
tracesSampleRate: process.env.NODE_ENV === "production" ? 0.1 : 1.0,
replaysSessionSampleRate: process.env.NODE_ENV === "production" ? 0.1 : 1.0,
replaysOnErrorSampleRate: 1.0,
});
export const onRouterTransitionStart = Sentry.captureRouterTransitionStart;
sentry.server.config.ts
import * as Sentry from "@sentry/nextjs";
Sentry.init({
dsn: process.env.SENTRY_DSN,
environment: process.env.NODE_ENV,
tracesSampleRate: process.env.NODE_ENV === 'production' ? 0.1 : 1.0,
});
sentry.edge.config.ts
import * as Sentry from "@sentry/nextjs";
Sentry.init({
dsn: process.env.SENTRY_DSN,
environment: process.env.NODE_ENV,
tracesSampleRate: process.env.NODE_ENV === 'production' ? 0.05 : 1.0,
});
instrumentation.ts
import * as Sentry from "@sentry/nextjs";
export async function register() {
if (process.env.NEXT_RUNTIME === "nodejs") {
await import("./sentry.server.config");
}
if (process.env.NEXT_RUNTIME === "edge") {
await import("./sentry.edge.config");
}
}
export const onRequestError = Sentry.captureRequestError;
What I've Tried
I'm aware of issue #4677 but most solutions there are deprecated in the latest SDK versions. I've attempted numerous approaches:
Deprecated/Non-working approaches from that discussion:
beforeNavigate - removed from SDK
Integrations.BrowserTracing - deprecated syntax
- Manual
startTransaction - deprecated API
- Various route normalization hooks - don't prevent the core issue
Modern attempts that also failed:
beforeStartSpan in browserTracingIntegration
beforeSendTransaction filtering
- Setting
disableManifestInjection: true
- Comprehensive transaction name normalization
- Dropping problematic transactions entirely
Expected Behavior
Transaction names should be:
/ for root page (regardless of locale)
/foo for foo page (regardless of locale)
/bar for bar page (regardless of locale)
With locale preserved as tags: i18n.locale: en/ar
Navigation should trigger Web Vitals:
<Link> clicks between pages
- Locale switching
- Programmatic navigation
Actual Behavior
English browsing:
/ → Transaction: /:locale (sometimes also /)
/foo → Transaction: /:locale
/bar → Transaction: /:locale
Arabic browsing:
/ar → Transaction: /:locale (should be /)
/ar/foo → Transaction: /:locale/foo (should be /foo)
/ar/bar → Transaction: /:locale/bar (should be /bar)
Navigation: No Web Vitals data for any client-side navigation.
Impact
This makes Sentry completely unusable for performance monitoring because:
- Cannot distinguish between pages - all English routes appear as
/:locale
- No navigation tracking - only page loads generate data
- Fragmented data - same logical pages have different transaction names per locale
- Cannot measure user journeys - no data for typical SPA navigation
Root Cause
The issue seems to be that Next.js 15 App Router with [locale] dynamic segments confuses Sentry's automatic instrumentation, which:
- Creates transactions based on file system routes (
/:locale) instead of actual URLs
- Doesn't properly handle
next-intl's localePrefix: "as-needed" routing
- Fails to track client-side navigation in i18n contexts
Questions
- Is there a working solution for Next.js 15 + App Router + next-intl?
- Should we avoid
localePrefix: "as-needed" entirely when using Sentry?
- Are there plans to fix i18n support in the Next.js SDK?
- Is there a way to completely override Sentry's automatic route detection?
This appears to be a fundamental compatibility issue between Sentry's Next.js integration and modern i18n patterns. Any guidance would be greatly appreciated.
Additional Context
- This issue affects production applications using common i18n patterns
- The problem makes Web Vitals monitoring completely unusable
- Similar issues exist but most solutions are deprecated in current SDK versions
- This seems like a critical compatibility gap that should be addressed
Labels to add when creating the issue:
bug
nextjs
performance
i18n
web-vitals
app-router
Steps to Reproduce
Steps to Reproduce
Prerequisites
- Node.js 18+
- Sentry account with a Next.js project created
- Basic understanding of Next.js App Router
Step 1: Create Next.js 15 App with App Router
npx create-next-app@latest sentry-i18n-bug --typescript --tailwind --eslint --app --no-src-dir
cd sentry-i18n-bug
Step 2: Install Required Dependencies
npm install @sentry/nextjs@10.14.0 next-intl@^4.3.7 rtl-detect@^1.1.2
Step 3: Set Up File Structure
Create the following file structure:
app/
├── [locale]/
│ ├── layout.tsx
│ ├── page.tsx
│ ├── hola/
│ │ └── page.tsx
│ └── products/
│ └── page.tsx
├── globals.css
├── favicon.ico
├── global-error.tsx
└── not-found.tsx
i18n/
├── routing.ts
└── request.ts
messages/
├── en.json
└── ar.json
middleware.ts
instrumentation.ts
instrumentation-client.ts
sentry.server.config.ts
sentry.edge.config.ts
next.config.ts
.env.local
Step 4: Create Configuration Files
4.1 Create i18n/routing.ts
import { defineRouting } from 'next-intl/routing';
export const routing = defineRouting({
locales: ['en', 'ar'],
defaultLocale: 'en',
localePrefix: 'as-needed', // This is the key setting that causes the issue
});
4.2 Create i18n/request.ts
import { routing } from './routing';
import { getRequestConfig } from 'next-intl/server';
export default getRequestConfig(async ({ requestLocale }) => {
let locale = await requestLocale;
if (!locale || !routing.locales.includes(locale as any)) {
locale = routing.defaultLocale;
}
return {
locale,
messages: (await import(`../messages/${locale}.json`)).default,
};
});
4.3 Create messages/en.json
{
"HomePage": {
"title": "Welcome to our website",
"description": "This is the English version"
},
"HolaPage": {
"title": "Hello Page",
"description": "This is the hello page in English"
},
"ProductsPage": {
"title": "Products",
"description": "Our products in English"
}
}
4.4 Create messages/ar.json
{
"HomePage": {
"title": "مرحباً بكم في موقعنا",
"description": "هذه هي النسخة العربية"
},
"HolaPage": {
"title": "صفحة مرحبا",
"description": "هذه صفحة الترحيب بالعربية"
},
"ProductsPage": {
"title": "المنتجات",
"description": "منتجاتنا بالعربية"
}
}
4.5 Create middleware.ts
import createMiddleware from 'next-intl/middleware';
import { routing } from './i18n/routing';
export default createMiddleware(routing);
export const config = {
matcher: ['/((?!api|_next|_vercel|.*\\..*).*)']
};
Step 5: Create App Router Pages
5.1 Create app/[locale]/layout.tsx
import { NextIntlClientProvider } from 'next-intl';
import { getMessages } from 'next-intl/server';
import { routing } from '@/i18n/routing';
import '../globals.css';
export function generateStaticParams() {
return routing.locales.map((locale) => ({ locale }));
}
export default async function RootLayout({
children,
params,
}: {
children: React.ReactNode;
params: Promise<{ locale: string }>;
}) {
const { locale } = await params;
const messages = await getMessages();
return (
<html lang={locale}>
<body>
<NextIntlClientProvider messages={messages}>
<nav style={{ padding: '1rem', borderBottom: '1px solid #ccc' }}>
<a href={`/${locale === 'en' ? '' : locale}`}>Home</a> |{' '}
<a href={`/${locale === 'en' ? '' : locale}${locale === 'en' ? '' : '/'}hola`}>Hola</a> |{' '}
<a href={`/${locale === 'en' ? '' : locale}${locale === 'en' ? '' : '/'}products`}>Products</a>
<div style={{ marginTop: '0.5rem' }}>
Language:
<a href="/" style={{ marginLeft: '0.5rem' }}>EN</a> |
<a href="/ar" style={{ marginLeft: '0.5rem' }}>AR</a>
</div>
</nav>
{children}
</NextIntlClientProvider>
</body>
</html>
);
}
5.2 Create app/[locale]/page.tsx
import { useTranslations } from 'next-intl';
export default function HomePage() {
const t = useTranslations('HomePage');
return (
<div style={{ padding: '2rem' }}>
<h1>{t('title')}</h1>
<p>{t('description')}</p>
<p>Current URL: <code>{typeof window !== 'undefined' ? window.location.pathname : 'Server'}</code></p>
</div>
);
}
5.3 Create app/[locale]/hola/page.tsx
import { useTranslations } from 'next-intl';
export default function HolaPage() {
const t = useTranslations('HolaPage');
return (
<div style={{ padding: '2rem' }}>
<h1>{t('title')}</h1>
<p>{t('description')}</p>
<p>Current URL: <code>{typeof window !== 'undefined' ? window.location.pathname : 'Server'}</code></p>
</div>
);
}
5.4 Create app/[locale]/products/page.tsx
import { useTranslations } from 'next-intl';
export default function ProductsPage() {
const t = useTranslations('ProductsPage');
return (
<div style={{ padding: '2rem' }}>
<h1>{t('title')}</h1>
<p>{t('description')}</p>
<p>Current URL: <code>{typeof window !== 'undefined' ? window.location.pathname : 'Server'}</code></p>
</div>
);
}
Step 6: Set Up Sentry Configuration Files
6.1 Create next.config.ts
import { withSentryConfig } from '@sentry/nextjs';
import { NextConfig } from 'next';
import createNextIntlPlugin from 'next-intl/plugin';
const nextConfig: NextConfig = {
// Add any Next.js config options here
};
const withNextIntl = createNextIntlPlugin({
requestConfig: './i18n/request.ts',
});
export default withSentryConfig(withNextIntl(nextConfig), {
org: "your-sentry-org",
project: "your-sentry-project",
authToken: process.env.SENTRY_AUTH_TOKEN,
silent: true,
});
6.2 Create instrumentation-client.ts
import * as Sentry from "@sentry/nextjs";
Sentry.init({
dsn: process.env.NEXT_PUBLIC_SENTRY_DSN,
environment: process.env.NODE_ENV,
integrations: [Sentry.replayIntegration()],
tracesSampleRate: process.env.NODE_ENV === "production" ? 0.1 : 1.0,
replaysSessionSampleRate: process.env.NODE_ENV === "production" ? 0.1 : 1.0,
replaysOnErrorSampleRate: 1.0,
});
export const onRouterTransitionStart = Sentry.captureRouterTransitionStart;
6.3 Create sentry.server.config.ts
import * as Sentry from "@sentry/nextjs";
Sentry.init({
dsn: process.env.SENTRY_DSN,
environment: process.env.NODE_ENV,
tracesSampleRate: process.env.NODE_ENV === 'production' ? 0.1 : 1.0,
});
6.4 Create sentry.edge.config.ts
import * as Sentry from "@sentry/nextjs";
Sentry.init({
dsn: process.env.SENTRY_DSN,
environment: process.env.NODE_ENV,
tracesSampleRate: process.env.NODE_ENV === 'production' ? 0.05 : 1.0,
});
6.5 Create instrumentation.ts
import * as Sentry from "@sentry/nextjs";
export async function register() {
if (process.env.NEXT_RUNTIME === "nodejs") {
await import("./sentry.server.config");
}
if (process.env.NEXT_RUNTIME === "edge") {
await import("./sentry.edge.config");
}
}
export const onRequestError = Sentry.captureRequestError;
Step 7: Set Up Environment Variables
Create .env.local:
# Get these from your Sentry project settings
NEXT_PUBLIC_SENTRY_DSN=https://your-key@your-sentry-url.ingest.us.sentry.io/your-project-id
SENTRY_DSN=https://your-key@your-sentry-url.ingest.us.sentry.io/your-project-id
SENTRY_AUTH_TOKEN=your-auth-token-here
Step 8: Build and Run the Application
npm run build
npm run start
Step 9: Reproduce the Bug
9.1 Test English Browsing (Default Locale)
- Open browser to
http://localhost:3000/
- Navigate to
http://localhost:3000/hola
- Navigate to
http://localhost:3000/products
- Check Sentry dashboard after 5-10 minutes
Expected: Transactions should be /, /hola, /products
Actual: All show as /:locale transaction
9.2 Test Arabic Browsing (Non-Default Locale)
- Open browser to
http://localhost:3000/ar
- Navigate to
http://localhost:3000/ar/hola
- Navigate to
http://localhost:3000/ar/products
- Check Sentry dashboard after 5-10 minutes
Expected: Transactions should be /, /hola, /products
Actual: Shows as /:locale, /:locale/hola, /:locale/products
9.3 Test Navigation Issues
- Open browser to
http://localhost:3000/
- Click on navigation links (don't use direct URL navigation)
- Switch languages using the EN/AR links
- Check Sentry dashboard
Expected: Each navigation should create Web Vitals data
Actual: Only page loads/refreshes create data, no navigation tracking
Expected Result
Expected Sentry Transaction Names:
- Root page:
/ (regardless of locale)
- Hola page:
/hola (regardless of locale)
- Products page:
/products (regardless of locale)
- Locale preserved as tags:
i18n.locale: en or i18n.locale: ar
Actual Result
Actual Sentry Transaction Names:
- English routes:
/:locale (all pages show as same transaction)
- Arabic routes:
/:locale, /:locale/hola, /:locale/products
- Sometimes duplicate transactions for same page
- No Web Vitals data for client-side navigation
Additional Context
Additional Notes
- The issue is most pronounced with
localePrefix: "as-needed"
- Changing to
localePrefix: "always" may reduce the issue but breaks URL structure requirements in my project.
- The problem affects both development and production builds
- Console logging in Sentry hooks may show normalization attempts, but final dashboard (inside insights-> Frontend -> Web Vitals) still shows wrong names
This reproduction case should demonstrate the exact issues described in the bug report.
Is there an existing issue for this?
How do you use Sentry?
Sentry Saas (sentry.io)
Which SDK are you using?
@sentry/nextjs
SDK Version
10.14.0
Framework Version
Next 15.5.2
Link to Sentry event
No response
Reproduction Example/SDK Setup
GitHub Issue: Sentry + Next.js 15 + next-intl Web Vitals Issue
Issue Title: Critical: Next.js 15 + next-intl App Router creates unusable Sentry Web Vitals - all English routes collapse to
/:locale, navigation not trackedRepository: https://github.com/getsentry/sentry-javascript/issues
Description
I'm experiencing a critical issue with Sentry Web Vitals in a Next.js 15 App Router application using
next-intlwithlocalePrefix: "as-needed". The transaction naming is completely broken, making performance monitoring unusable.Environment
@sentry/nextjs@10.14.015.5.2^4.3.7app/[locale]/structureCritical Issues
1. All English routes collapse to
/:localeWhen browsing in English (default locale, no URL prefix):
/creates transaction:/:locale/foocreates transaction:/:locale/barcreates transaction:/:locale2. Arabic routes get parameterized patterns
When browsing in Arabic (with
/arprefix):/ar/foocreates transaction:/:locale/foo/foofor proper grouping3. Duplicate transactions for root page
/(English) sometimes creates BOTH:/(correct)/:locale(incorrect duplicate)4. Next.js navigation completely broken
/to/ar) doesn't create Web Vitals transactions<Link>navigation between pages doesn't trigger Web VitalsCurrent Configuration
next.config.ts
instrumentation-client.ts
sentry.server.config.ts
sentry.edge.config.ts
instrumentation.ts
What I've Tried
I'm aware of issue #4677 but most solutions there are deprecated in the latest SDK versions. I've attempted numerous approaches:
Deprecated/Non-working approaches from that discussion:
beforeNavigate- removed from SDKIntegrations.BrowserTracing- deprecated syntaxstartTransaction- deprecated APIModern attempts that also failed:
beforeStartSpaninbrowserTracingIntegrationbeforeSendTransactionfilteringdisableManifestInjection: trueExpected Behavior
Transaction names should be:
/for root page (regardless of locale)/foofor foo page (regardless of locale)/barfor bar page (regardless of locale)With locale preserved as tags:
i18n.locale: en/arNavigation should trigger Web Vitals:
<Link>clicks between pagesActual Behavior
English browsing:
Arabic browsing:
Navigation: No Web Vitals data for any client-side navigation.
Impact
This makes Sentry completely unusable for performance monitoring because:
/:localeRoot Cause
The issue seems to be that Next.js 15 App Router with
[locale]dynamic segments confuses Sentry's automatic instrumentation, which:/:locale) instead of actual URLsnext-intl'slocalePrefix: "as-needed"routingQuestions
localePrefix: "as-needed"entirely when using Sentry?This appears to be a fundamental compatibility issue between Sentry's Next.js integration and modern i18n patterns. Any guidance would be greatly appreciated.
Additional Context
Labels to add when creating the issue:
bugnextjsperformancei18nweb-vitalsapp-routerSteps to Reproduce
Steps to Reproduce
Prerequisites
Step 1: Create Next.js 15 App with App Router
npx create-next-app@latest sentry-i18n-bug --typescript --tailwind --eslint --app --no-src-dir cd sentry-i18n-bugStep 2: Install Required Dependencies
Step 3: Set Up File Structure
Create the following file structure:
Step 4: Create Configuration Files
4.1 Create
i18n/routing.ts4.2 Create
i18n/request.ts4.3 Create
messages/en.json{ "HomePage": { "title": "Welcome to our website", "description": "This is the English version" }, "HolaPage": { "title": "Hello Page", "description": "This is the hello page in English" }, "ProductsPage": { "title": "Products", "description": "Our products in English" } }4.4 Create
messages/ar.json{ "HomePage": { "title": "مرحباً بكم في موقعنا", "description": "هذه هي النسخة العربية" }, "HolaPage": { "title": "صفحة مرحبا", "description": "هذه صفحة الترحيب بالعربية" }, "ProductsPage": { "title": "المنتجات", "description": "منتجاتنا بالعربية" } }4.5 Create
middleware.tsStep 5: Create App Router Pages
5.1 Create
app/[locale]/layout.tsx5.2 Create
app/[locale]/page.tsx5.3 Create
app/[locale]/hola/page.tsx5.4 Create
app/[locale]/products/page.tsxStep 6: Set Up Sentry Configuration Files
6.1 Create
next.config.ts6.2 Create
instrumentation-client.ts6.3 Create
sentry.server.config.ts6.4 Create
sentry.edge.config.ts6.5 Create
instrumentation.tsStep 7: Set Up Environment Variables
Create
.env.local:# Get these from your Sentry project settings NEXT_PUBLIC_SENTRY_DSN=https://your-key@your-sentry-url.ingest.us.sentry.io/your-project-id SENTRY_DSN=https://your-key@your-sentry-url.ingest.us.sentry.io/your-project-id SENTRY_AUTH_TOKEN=your-auth-token-hereStep 8: Build and Run the Application
Step 9: Reproduce the Bug
9.1 Test English Browsing (Default Locale)
http://localhost:3000/http://localhost:3000/holahttp://localhost:3000/productsExpected: Transactions should be
/,/hola,/productsActual: All show as
/:localetransaction9.2 Test Arabic Browsing (Non-Default Locale)
http://localhost:3000/arhttp://localhost:3000/ar/holahttp://localhost:3000/ar/productsExpected: Transactions should be
/,/hola,/productsActual: Shows as
/:locale,/:locale/hola,/:locale/products9.3 Test Navigation Issues
http://localhost:3000/Expected: Each navigation should create Web Vitals data
Actual: Only page loads/refreshes create data, no navigation tracking
Expected Result
Expected Sentry Transaction Names:
/(regardless of locale)/hola(regardless of locale)/products(regardless of locale)i18n.locale: enori18n.locale: arActual Result
Actual Sentry Transaction Names:
/:locale(all pages show as same transaction)/:locale,/:locale/hola,/:locale/productsAdditional Context
Additional Notes
localePrefix: "as-needed"localePrefix: "always"may reduce the issue but breaks URL structure requirements in my project.This reproduction case should demonstrate the exact issues described in the bug report.