Skip to content

feat(nextjs): Inject manifest into client for webpack builds #16857

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 19 commits into from
Jul 15, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions packages/nextjs/src/config/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -467,6 +467,24 @@ export type SentryBuildOptions = {
*/
suppressOnRouterTransitionStartWarning?: boolean;

/**
* Disables automatic injection of the route manifest into the client bundle.
*
* The route manifest is a build-time generated mapping of your Next.js App Router
* routes that enables Sentry to group transactions by parameterized route names
* (e.g., `/users/:id` instead of `/users/123`, `/users/456`, etc.).
*
* **Disable this option if:**
* - You want to minimize client bundle size
* - You're experiencing build issues related to route scanning
* - You're using custom routing that the scanner can't detect
* - You prefer raw URLs in transaction names
* - You're only using Pages Router (this feature is only supported in the App Router)
*
* @default false
*/
disableManifestInjection?: boolean;
Copy link
Member

Choose a reason for hiding this comment

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

l/feel free to ignore: I generally prefer enableX with default true over disableX with default false flag patterns but not sure if there's already precedence in other options here. To me the enableX pattern is a bit more intuitive from

  • a user perspective because they can switch on/off a feature without having to invert it in their mind
  • a dev perspective because I don't have to invert the ifs (i.e. if enableX doX()).

But again, this might be just me, so happy to leave this up to you and no objections to going withdisable here.


/**
* Contains a set of experimental flags that might change in future releases. These flags enable
* features that are still in development and may be modified, renamed, or removed without notice.
Expand Down
6 changes: 5 additions & 1 deletion packages/nextjs/src/config/webpack.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import * as fs from 'fs';
import * as path from 'path';
import { sync as resolveSync } from 'resolve';
import type { VercelCronsConfig } from '../common/types';
import type { RouteManifest } from './manifest/types';
// Note: If you need to import a type from Webpack, do it in `types.ts` and export it from there. Otherwise, our
// circular dependency check thinks this file is importing from itself. See https://github.com/pahen/madge/issues/306.
import type {
Expand Down Expand Up @@ -43,6 +44,7 @@ export function constructWebpackConfigFunction(
userNextConfig: NextConfigObject = {},
userSentryOptions: SentryBuildOptions = {},
releaseName: string | undefined,
routeManifest: RouteManifest | undefined,
): WebpackConfigFunction {
// Will be called by nextjs and passed its default webpack configuration and context data about the build (whether
// we're building server or client, whether we're in dev, what version of webpack we're using, etc). Note that
Expand Down Expand Up @@ -88,7 +90,7 @@ export function constructWebpackConfigFunction(
const newConfig = setUpModuleRules(rawNewConfig);

// Add a loader which will inject code that sets global values
addValueInjectionLoader(newConfig, userNextConfig, userSentryOptions, buildContext, releaseName);
addValueInjectionLoader(newConfig, userNextConfig, userSentryOptions, buildContext, releaseName, routeManifest);

addOtelWarningIgnoreRule(newConfig);

Expand Down Expand Up @@ -686,6 +688,7 @@ function addValueInjectionLoader(
userSentryOptions: SentryBuildOptions,
buildContext: BuildContext,
releaseName: string | undefined,
routeManifest: RouteManifest | undefined,
): void {
const assetPrefix = userNextConfig.assetPrefix || userNextConfig.basePath || '';

Expand Down Expand Up @@ -727,6 +730,7 @@ function addValueInjectionLoader(
_sentryExperimentalThirdPartyOriginStackFrames: userSentryOptions._experimental?.thirdPartyOriginStackFrames
? 'true'
: undefined,
_sentryRouteManifest: JSON.stringify(routeManifest),
};

if (buildContext.isServer) {
Expand Down
14 changes: 13 additions & 1 deletion packages/nextjs/src/config/withSentryConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import { getSentryRelease } from '@sentry/node';
import * as childProcess from 'child_process';
import * as fs from 'fs';
import * as path from 'path';
import { createRouteManifest } from './manifest/createRouteManifest';
import type { RouteManifest } from './manifest/types';
import type {
ExportedNextConfig as NextConfig,
NextConfigFunction,
Expand Down Expand Up @@ -141,6 +143,11 @@ function getFinalConfigObject(
}
}

let routeManifest: RouteManifest | undefined;
if (!userSentryOptions.disableManifestInjection) {
routeManifest = createRouteManifest();
}

setUpBuildTimeVariables(incomingUserNextConfigObject, userSentryOptions, releaseName);

const nextJsVersion = getNextjsVersion();
Expand Down Expand Up @@ -300,7 +307,12 @@ function getFinalConfigObject(
],
},
}),
webpack: constructWebpackConfigFunction(incomingUserNextConfigObject, userSentryOptions, releaseName),
webpack: constructWebpackConfigFunction(
incomingUserNextConfigObject,
userSentryOptions,
releaseName,
routeManifest,
),
};
}

Expand Down
Loading