Skip to content

feat(nextjs): Inject manifest into client for turbopack builds #16902

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 29 commits into from
Jul 15, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
73e0637
build manifest function
chargome Jul 8, 2025
9b28d79
tests
chargome Jul 8, 2025
657dd55
rename file
chargome Jul 8, 2025
a82b977
simplify page check
chargome Jul 8, 2025
7d6e198
que
chargome Jul 9, 2025
71b1121
fix handling optional catchall routes
chargome Jul 9, 2025
3114f5c
update if else block
chargome Jul 9, 2025
75305f5
..
chargome Jul 9, 2025
4ad310f
inject manifest
chargome Jul 9, 2025
e105620
update interface + rename func
chargome Jul 9, 2025
685bf04
Merge branch 'cg-next-client-manifest' into cg-next-manifest-webpack
chargome Jul 9, 2025
f593c49
Merge branch 'develop' into cg-next-client-manifest
chargome Jul 9, 2025
2a4a35c
Merge branch 'cg-next-client-manifest' into cg-next-manifest-webpack
chargome Jul 9, 2025
f9430fe
rename option
chargome Jul 9, 2025
4c0d008
add valueinjection to turbopack
chargome Jul 10, 2025
de44fe0
merge stuff
chargome Jul 10, 2025
6071f76
split routes again
chargome Jul 11, 2025
e416a2c
add option for route-groups
chargome Jul 11, 2025
78ff933
Merge branch 'cg-next-client-manifest' into cg-next-manifest-webpack
chargome Jul 11, 2025
06be02e
Merge branch 'cg-next-manifest-webpack' into cg-next-manifest-turbopack
chargome Jul 11, 2025
aeb2e6d
Merge branch 'develop' into cg-next-manifest-webpack
chargome Jul 11, 2025
1d9089f
Merge branch 'cg-next-manifest-webpack' into cg-next-manifest-turbopack
chargome Jul 11, 2025
b01ddc7
conditionally add turbo
chargome Jul 14, 2025
b62bdb6
Update packages/nextjs/src/config/turbopack/constructTurbopackConfig.ts
chargome Jul 14, 2025
16eb903
Update packages/nextjs/src/config/turbopack/constructTurbopackConfig.ts
chargome Jul 14, 2025
f4dbaca
add log for existing matcher
chargome Jul 14, 2025
7159fcb
windows path + update tests
chargome Jul 14, 2025
36322c3
.
chargome Jul 14, 2025
87910e6
Merge branch 'develop' into cg-next-manifest-turbopack
chargome Jul 15, 2025
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
78 changes: 78 additions & 0 deletions packages/nextjs/src/config/turbopack/constructTurbopackConfig.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import { logger } from '@sentry/core';
import * as chalk from 'chalk';
import * as path from 'path';
import type { RouteManifest } from '../manifest/types';
import type { NextConfigObject, TurbopackOptions, TurbopackRuleConfigItemOrShortcut } from '../types';

/**
* Construct a Turbopack config object from a Next.js config object and a Turbopack options object.
*
* @param userNextConfig - The Next.js config object.
* @param turbopackOptions - The Turbopack options object.
* @returns The Turbopack config object.
*/
export function constructTurbopackConfig({
userNextConfig,
routeManifest,
}: {
userNextConfig: NextConfigObject;
routeManifest?: RouteManifest;
}): TurbopackOptions {
const newConfig: TurbopackOptions = {
...userNextConfig.turbopack,
};

if (routeManifest) {
newConfig.rules = safelyAddTurbopackRule(newConfig.rules, {
matcher: '**/instrumentation-client.*',
rule: {
loaders: [
{
loader: path.resolve(__dirname, '..', 'loaders', 'valueInjectionLoader.js'),
options: {
values: {
_sentryRouteManifest: JSON.stringify(routeManifest),
},
},
},
],
},
});
}

return newConfig;
}

/**
* Safely add a Turbopack rule to the existing rules.
*
* @param existingRules - The existing rules.
* @param matcher - The matcher for the rule.
* @param rule - The rule to add.
* @returns The updated rules object.
*/
export function safelyAddTurbopackRule(
existingRules: TurbopackOptions['rules'],
{ matcher, rule }: { matcher: string; rule: TurbopackRuleConfigItemOrShortcut },
): TurbopackOptions['rules'] {
if (!existingRules) {
return {
[matcher]: rule,
};
}

// If the rule already exists, we don't want to mess with it.
if (existingRules[matcher]) {
logger.info(
`${chalk.cyan(
'info',
)} - Turbopack rule already exists for ${matcher}. Please remove it from your Next.js config in order for Sentry to work properly.`,
);
return existingRules;
}

return {
...existingRules,
[matcher]: rule,
};
}
1 change: 1 addition & 0 deletions packages/nextjs/src/config/turbopack/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './constructTurbopackConfig';
36 changes: 36 additions & 0 deletions packages/nextjs/src/config/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ export type NextConfigObject = {
// https://nextjs.org/docs/pages/api-reference/next-config-js/env
env?: Record<string, string>;
serverExternalPackages?: string[]; // next >= v15.0.0
turbopack?: TurbopackOptions;
};

export type SentryBuildOptions = {
Expand Down Expand Up @@ -607,3 +608,38 @@ export type EnhancedGlobal = typeof GLOBAL_OBJ & {
SENTRY_RELEASE?: { id: string };
SENTRY_RELEASES?: { [key: string]: { id: string } };
};

type JSONValue = string | number | boolean | JSONValue[] | { [k: string]: JSONValue };

type TurbopackLoaderItem =
| string
| {
loader: string;
// At the moment, Turbopack options must be JSON-serializable, so restrict values.
options: Record<string, JSONValue>;
};

type TurbopackRuleCondition = {
path: string | RegExp;
};

export type TurbopackRuleConfigItemOrShortcut = TurbopackLoaderItem[] | TurbopackRuleConfigItem;

type TurbopackRuleConfigItemOptions = {
loaders: TurbopackLoaderItem[];
as?: string;
};

type TurbopackRuleConfigItem =
| TurbopackRuleConfigItemOptions
| { [condition: string]: TurbopackRuleConfigItem }
| false;

export interface TurbopackOptions {
resolveAlias?: Record<string, string | string[] | Record<string, string | string[]>>;
resolveExtensions?: string[];
rules?: Record<string, TurbopackRuleConfigItemOrShortcut>;
conditions?: Record<string, TurbopackRuleCondition>;
moduleIds?: 'named' | 'deterministic';
root?: string;
}
23 changes: 16 additions & 7 deletions packages/nextjs/src/config/withSentryConfig.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 { createRouteManifest } from './manifest/createRouteManifest';
import type { RouteManifest } from './manifest/types';
import { constructTurbopackConfig } from './turbopack';
import type {
ExportedNextConfig as NextConfig,
NextConfigFunction,
Expand Down Expand Up @@ -251,6 +252,8 @@ function getFinalConfigObject(
}

let nextMajor: number | undefined;
const isTurbopack = process.env.TURBOPACK;
let isTurbopackSupported = false;
if (nextJsVersion) {
const { major, minor, patch, prerelease } = parseSemver(nextJsVersion);
nextMajor = major;
Expand All @@ -262,6 +265,7 @@ function getFinalConfigObject(
(major === 15 && minor > 3) ||
(major === 15 && minor === 3 && patch === 0 && prerelease === undefined) ||
(major === 15 && minor === 3 && patch > 0));
isTurbopackSupported = isSupportedVersion;
const isSupportedCanary =
major !== undefined &&
minor !== undefined &&
Expand All @@ -274,7 +278,7 @@ function getFinalConfigObject(
parseInt(prerelease.split('.')[1] || '', 10) >= 28;
const supportsClientInstrumentation = isSupportedCanary || isSupportedVersion;

if (!supportsClientInstrumentation && process.env.TURBOPACK) {
if (!supportsClientInstrumentation && isTurbopack) {
if (process.env.NODE_ENV === 'development') {
// eslint-disable-next-line no-console
console.warn(
Expand Down Expand Up @@ -307,12 +311,17 @@ function getFinalConfigObject(
],
},
}),
webpack: constructWebpackConfigFunction(
incomingUserNextConfigObject,
userSentryOptions,
releaseName,
routeManifest,
),
webpack: !isTurbopack
? constructWebpackConfigFunction(incomingUserNextConfigObject, userSentryOptions, releaseName, routeManifest)
: undefined,
...(isTurbopackSupported && isTurbopack
? {
turbopack: constructTurbopackConfig({
userNextConfig: incomingUserNextConfigObject,
routeManifest,
}),
}
: {}),
};
}

Expand Down
Loading
Loading