Skip to content

Commit bcb6f82

Browse files
committed
fix: Mark ISR pages for client side to ignore meta trace contents
1 parent 2deb000 commit bcb6f82

File tree

4 files changed

+96
-13
lines changed

4 files changed

+96
-13
lines changed

packages/nextjs/src/client/routing/appRouterRoutingInstrumentation.ts

Lines changed: 56 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {
77
SEMANTIC_ATTRIBUTE_SENTRY_SOURCE,
88
} from '@sentry/core';
99
import { startBrowserTracingNavigationSpan, startBrowserTracingPageLoadSpan, WINDOW } from '@sentry/react';
10+
import type { RouteManifest } from '../../config/manifest/types';
1011
import { maybeParameterizeRoute } from './parameterization';
1112

1213
export const INCOMPLETE_APP_ROUTER_INSTRUMENTATION_TRANSACTION_NAME = 'incomplete-app-router-transaction';
@@ -33,20 +34,66 @@ let navigationRoutingMode: 'router-patch' | 'transition-start-hook' = 'router-pa
3334

3435
const currentRouterPatchingNavigationSpanRef: NavigationSpanRef = { current: undefined };
3536

37+
/**
38+
* Check if the current route is an ISR/SSG page by looking it up in the route manifest
39+
*/
40+
function isIsrSsgRoute(pathname: string): boolean {
41+
const globalWithManifest = GLOBAL_OBJ as typeof GLOBAL_OBJ & {
42+
_sentryRouteManifest?: string | RouteManifest;
43+
};
44+
45+
const manifestData = globalWithManifest._sentryRouteManifest;
46+
if (!manifestData) {
47+
return false;
48+
}
49+
50+
let manifest: RouteManifest;
51+
if (typeof manifestData === 'string') {
52+
try {
53+
manifest = JSON.parse(manifestData);
54+
} catch {
55+
return false;
56+
}
57+
} else {
58+
manifest = manifestData;
59+
}
60+
61+
if (!manifest.isrRoutes || manifest.isrRoutes.length === 0) {
62+
return false;
63+
}
64+
65+
// Check if the pathname matches any ISR route
66+
// For dynamic routes, we need to match the parameterized pattern
67+
const parameterizedPath = maybeParameterizeRoute(pathname);
68+
const pathToCheck = parameterizedPath || pathname;
69+
70+
return manifest.isrRoutes.includes(pathToCheck);
71+
}
72+
3673
/** Instruments the Next.js app router for pageloads. */
3774
export function appRouterInstrumentPageLoad(client: Client): void {
3875
const parameterizedPathname = maybeParameterizeRoute(WINDOW.location.pathname);
3976
const origin = browserPerformanceTimeOrigin();
40-
startBrowserTracingPageLoadSpan(client, {
41-
name: parameterizedPathname ?? WINDOW.location.pathname,
42-
// pageload should always start at timeOrigin (and needs to be in s, not ms)
43-
startTime: origin ? origin / 1000 : undefined,
44-
attributes: {
45-
[SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'pageload',
46-
[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.pageload.nextjs.app_router_instrumentation',
47-
[SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: parameterizedPathname ? 'route' : 'url',
77+
78+
// Check if this is an ISR/SSG page
79+
// if so, don't use cached trace meta tags to prevent using cached trace data
80+
const isIsrSsgPage = isIsrSsgRoute(WINDOW.location.pathname);
81+
82+
startBrowserTracingPageLoadSpan(
83+
client,
84+
{
85+
name: parameterizedPathname ?? WINDOW.location.pathname,
86+
// pageload should always start at timeOrigin (and needs to be in s, not ms)
87+
startTime: origin ? origin / 1000 : undefined,
88+
attributes: {
89+
[SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'pageload',
90+
[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.pageload.nextjs.app_router_instrumentation',
91+
[SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: parameterizedPathname ? 'route' : 'url',
92+
},
4893
},
49-
});
94+
// For ISR/SSG pages, pass empty trace data to prevent using cached meta tags
95+
isIsrSsgPage ? { sentryTrace: undefined, baggage: undefined } : undefined,
96+
);
5097
}
5198

5299
interface NavigationSpanRef {

packages/nextjs/src/client/routing/parameterization.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@ function getManifest(): RouteManifest | null {
9696
let manifest: RouteManifest = {
9797
staticRoutes: [],
9898
dynamicRoutes: [],
99+
isrRoutes: [],
99100
};
100101

101102
// Shallow check if the manifest is actually what we expect it to be

packages/nextjs/src/config/manifest/createRouteManifest.ts

Lines changed: 34 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -115,23 +115,46 @@ function hasOptionalPrefix(paramNames: string[]): boolean {
115115
return firstParam === 'locale' || firstParam === 'lang' || firstParam === 'language';
116116
}
117117

118+
/**
119+
* Check if a page file exports generateStaticParams (ISR/SSG indicator)
120+
*/
121+
function checkForGenerateStaticParams(pageFilePath: string): boolean {
122+
try {
123+
const content = fs.readFileSync(pageFilePath, 'utf8');
124+
// check for generateStaticParams export
125+
// the regex covers `export function generateStaticParams`, `export async function generateStaticParams`, `export const generateStaticParams`
126+
return /export\s+(async\s+)?function\s+generateStaticParams|export\s+const\s+generateStaticParams/.test(content);
127+
} catch {
128+
return false;
129+
}
130+
}
131+
118132
function scanAppDirectory(
119133
dir: string,
120134
basePath: string = '',
121135
includeRouteGroups: boolean = false,
122-
): { dynamicRoutes: RouteInfo[]; staticRoutes: RouteInfo[] } {
136+
): { dynamicRoutes: RouteInfo[]; staticRoutes: RouteInfo[]; isrRoutes: string[] } {
123137
const dynamicRoutes: RouteInfo[] = [];
124138
const staticRoutes: RouteInfo[] = [];
139+
const isrRoutes: string[] = [];
125140

126141
try {
127142
const entries = fs.readdirSync(dir, { withFileTypes: true });
128-
const pageFile = entries.some(entry => isPageFile(entry.name));
143+
const pageFile = entries.find(entry => isPageFile(entry.name));
129144

130145
if (pageFile) {
131146
// Conditionally normalize the path based on includeRouteGroups option
132147
const routePath = includeRouteGroups ? basePath || '/' : normalizeRoutePath(basePath || '/');
133148
const isDynamic = routePath.includes(':');
134149

150+
// Check if this page has generateStaticParams (ISR/SSG indicator)
151+
const pageFilePath = path.join(dir, pageFile.name);
152+
const hasGenerateStaticParams = checkForGenerateStaticParams(pageFilePath);
153+
154+
if (hasGenerateStaticParams) {
155+
isrRoutes.push(routePath);
156+
}
157+
135158
if (isDynamic) {
136159
const { regex, paramNames, hasOptionalPrefix } = buildRegexForDynamicRoute(routePath);
137160
dynamicRoutes.push({
@@ -172,14 +195,15 @@ function scanAppDirectory(
172195

173196
dynamicRoutes.push(...subRoutes.dynamicRoutes);
174197
staticRoutes.push(...subRoutes.staticRoutes);
198+
isrRoutes.push(...subRoutes.isrRoutes);
175199
}
176200
}
177201
} catch (error) {
178202
// eslint-disable-next-line no-console
179203
console.warn('Error building route manifest:', error);
180204
}
181205

182-
return { dynamicRoutes, staticRoutes };
206+
return { dynamicRoutes, staticRoutes, isrRoutes };
183207
}
184208

185209
/**
@@ -204,6 +228,7 @@ export function createRouteManifest(options?: CreateRouteManifestOptions): Route
204228

205229
if (!targetDir) {
206230
return {
231+
isrRoutes: [],
207232
dynamicRoutes: [],
208233
staticRoutes: [],
209234
};
@@ -214,11 +239,16 @@ export function createRouteManifest(options?: CreateRouteManifestOptions): Route
214239
return manifestCache;
215240
}
216241

217-
const { dynamicRoutes, staticRoutes } = scanAppDirectory(targetDir, options?.basePath, options?.includeRouteGroups);
242+
const { dynamicRoutes, staticRoutes, isrRoutes } = scanAppDirectory(
243+
targetDir,
244+
options?.basePath,
245+
options?.includeRouteGroups,
246+
);
218247

219248
const manifest: RouteManifest = {
220249
dynamicRoutes,
221250
staticRoutes,
251+
isrRoutes,
222252
};
223253

224254
// set cache

packages/nextjs/src/config/manifest/types.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,4 +34,9 @@ export type RouteManifest = {
3434
* List of all static routes
3535
*/
3636
staticRoutes: RouteInfo[];
37+
38+
/**
39+
* List of ISR/SSG routes (routes with generateStaticParams)
40+
*/
41+
isrRoutes: string[];
3742
};

0 commit comments

Comments
 (0)