Skip to content

Commit c72b29a

Browse files
committed
fix: route manifest is always a string
1 parent f46d817 commit c72b29a

File tree

2 files changed

+268
-10
lines changed

2 files changed

+268
-10
lines changed

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

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,27 +3,23 @@ import type { RouteManifest } from '../../config/manifest/types';
33
import { maybeParameterizeRoute } from './parameterization';
44

55
const globalWithInjectedValues = WINDOW as typeof WINDOW & {
6-
_sentryRouteManifest: string | RouteManifest;
6+
_sentryRouteManifest: string;
77
};
88

99
/**
1010
* Check if the current page is an ISR/SSG route by checking the route manifest.
1111
*/
1212
function isIsrSsgRoute(pathname: string): boolean {
1313
const manifestData = globalWithInjectedValues._sentryRouteManifest;
14-
if (!manifestData) {
14+
if (!manifestData || typeof manifestData !== 'string') {
1515
return false;
1616
}
1717

1818
let manifest: RouteManifest;
19-
if (typeof manifestData === 'string') {
20-
try {
21-
manifest = JSON.parse(manifestData);
22-
} catch {
23-
return false;
24-
}
25-
} else {
26-
manifest = manifestData;
19+
try {
20+
manifest = JSON.parse(manifestData);
21+
} catch {
22+
return false;
2723
}
2824

2925
if (!manifest.isrRoutes || !Array.isArray(manifest.isrRoutes) || manifest.isrRoutes.length === 0) {
Lines changed: 262 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,262 @@
1+
import { WINDOW } from '@sentry/react';
2+
import { JSDOM } from 'jsdom';
3+
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
4+
import { removeIsrSsgTraceMetaTags } from '../../src/client/routing/isrRoutingTracing';
5+
import type { RouteManifest } from '../../src/config/manifest/types';
6+
7+
const globalWithInjectedValues = WINDOW as typeof WINDOW & {
8+
_sentryRouteManifest: string;
9+
};
10+
11+
describe('isrRoutingTracing', () => {
12+
let dom: JSDOM;
13+
14+
beforeEach(() => {
15+
// Set up a fresh DOM environment for each test
16+
dom = new JSDOM('<!DOCTYPE html><html><head></head><body></body></html>', {
17+
url: 'https://example.com/',
18+
});
19+
Object.defineProperty(global, 'document', { value: dom.window.document, writable: true });
20+
Object.defineProperty(global, 'location', { value: dom.window.location, writable: true });
21+
22+
// Clear the injected manifest
23+
delete globalWithInjectedValues._sentryRouteManifest;
24+
});
25+
26+
afterEach(() => {
27+
// Clean up
28+
vi.clearAllMocks();
29+
});
30+
31+
describe('removeIsrSsgTraceMetaTags', () => {
32+
const mockManifest: RouteManifest = {
33+
staticRoutes: [{ path: '/' }, { path: '/blog' }],
34+
dynamicRoutes: [
35+
{
36+
path: '/products/:id',
37+
regex: '^/products/([^/]+?)(?:/)?$',
38+
paramNames: ['id'],
39+
hasOptionalPrefix: false,
40+
},
41+
{
42+
path: '/posts/:slug',
43+
regex: '^/posts/([^/]+?)(?:/)?$',
44+
paramNames: ['slug'],
45+
hasOptionalPrefix: false,
46+
},
47+
],
48+
isrRoutes: ['/', '/blog', '/products/:id', '/posts/:slug'],
49+
};
50+
51+
it('should remove meta tags when on a static ISR route', () => {
52+
// Set up DOM with meta tags
53+
const sentryTraceMeta = dom.window.document.createElement('meta');
54+
sentryTraceMeta.setAttribute('name', 'sentry-trace');
55+
sentryTraceMeta.setAttribute('content', 'trace-id-12345');
56+
dom.window.document.head.appendChild(sentryTraceMeta);
57+
58+
const baggageMeta = dom.window.document.createElement('meta');
59+
baggageMeta.setAttribute('name', 'baggage');
60+
baggageMeta.setAttribute('content', 'sentry-trace-id=12345');
61+
dom.window.document.head.appendChild(baggageMeta);
62+
63+
// Set up route manifest (as stringified JSON, which is how it's injected in production)
64+
globalWithInjectedValues._sentryRouteManifest = JSON.stringify(mockManifest);
65+
66+
// Set location to an ISR route
67+
Object.defineProperty(global, 'location', {
68+
value: { ...dom.window.location, pathname: '/blog' },
69+
writable: true,
70+
});
71+
72+
// Call the function
73+
removeIsrSsgTraceMetaTags();
74+
75+
// Verify meta tags were removed
76+
expect(dom.window.document.querySelector('meta[name="sentry-trace"]')).toBeNull();
77+
expect(dom.window.document.querySelector('meta[name="baggage"]')).toBeNull();
78+
});
79+
80+
it('should remove meta tags when on a dynamic ISR route', () => {
81+
// Set up DOM with meta tags
82+
const sentryTraceMeta = dom.window.document.createElement('meta');
83+
sentryTraceMeta.setAttribute('name', 'sentry-trace');
84+
sentryTraceMeta.setAttribute('content', 'trace-id-12345');
85+
dom.window.document.head.appendChild(sentryTraceMeta);
86+
87+
const baggageMeta = dom.window.document.createElement('meta');
88+
baggageMeta.setAttribute('name', 'baggage');
89+
baggageMeta.setAttribute('content', 'sentry-trace-id=12345');
90+
dom.window.document.head.appendChild(baggageMeta);
91+
92+
// Set up route manifest
93+
globalWithInjectedValues._sentryRouteManifest = JSON.stringify(mockManifest);
94+
95+
// Set location to a dynamic ISR route
96+
Object.defineProperty(global, 'location', {
97+
value: { ...dom.window.location, pathname: '/products/123' },
98+
writable: true,
99+
});
100+
101+
// Call the function
102+
removeIsrSsgTraceMetaTags();
103+
104+
// Verify meta tags were removed
105+
expect(dom.window.document.querySelector('meta[name="sentry-trace"]')).toBeNull();
106+
expect(dom.window.document.querySelector('meta[name="baggage"]')).toBeNull();
107+
});
108+
109+
it('should NOT remove meta tags when on a non-ISR route', () => {
110+
// Set up DOM with meta tags
111+
const sentryTraceMeta = dom.window.document.createElement('meta');
112+
sentryTraceMeta.setAttribute('name', 'sentry-trace');
113+
sentryTraceMeta.setAttribute('content', 'trace-id-12345');
114+
dom.window.document.head.appendChild(sentryTraceMeta);
115+
116+
const baggageMeta = dom.window.document.createElement('meta');
117+
baggageMeta.setAttribute('name', 'baggage');
118+
baggageMeta.setAttribute('content', 'sentry-trace-id=12345');
119+
dom.window.document.head.appendChild(baggageMeta);
120+
121+
// Set up route manifest
122+
globalWithInjectedValues._sentryRouteManifest = JSON.stringify(mockManifest);
123+
124+
// Set location to a non-ISR route
125+
Object.defineProperty(global, 'location', {
126+
value: { ...dom.window.location, pathname: '/regular-page' },
127+
writable: true,
128+
});
129+
130+
// Call the function
131+
removeIsrSsgTraceMetaTags();
132+
133+
// Verify meta tags were NOT removed
134+
expect(dom.window.document.querySelector('meta[name="sentry-trace"]')).not.toBeNull();
135+
expect(dom.window.document.querySelector('meta[name="baggage"]')).not.toBeNull();
136+
});
137+
138+
it('should handle missing manifest gracefully', () => {
139+
// Set up DOM with meta tags
140+
const sentryTraceMeta = dom.window.document.createElement('meta');
141+
sentryTraceMeta.setAttribute('name', 'sentry-trace');
142+
sentryTraceMeta.setAttribute('content', 'trace-id-12345');
143+
dom.window.document.head.appendChild(sentryTraceMeta);
144+
145+
// No manifest set
146+
// globalWithInjectedValues._sentryRouteManifest is undefined
147+
148+
// Set location
149+
Object.defineProperty(global, 'location', {
150+
value: { ...dom.window.location, pathname: '/blog' },
151+
writable: true,
152+
});
153+
154+
// Call the function (should not throw)
155+
expect(() => removeIsrSsgTraceMetaTags()).not.toThrow();
156+
157+
// Verify meta tags were NOT removed (no manifest means no ISR detection)
158+
expect(dom.window.document.querySelector('meta[name="sentry-trace"]')).not.toBeNull();
159+
});
160+
161+
it('should handle invalid JSON manifest gracefully', () => {
162+
// Set up DOM with meta tags
163+
const sentryTraceMeta = dom.window.document.createElement('meta');
164+
sentryTraceMeta.setAttribute('name', 'sentry-trace');
165+
sentryTraceMeta.setAttribute('content', 'trace-id-12345');
166+
dom.window.document.head.appendChild(sentryTraceMeta);
167+
168+
// Set up invalid manifest
169+
globalWithInjectedValues._sentryRouteManifest = 'invalid json {';
170+
171+
// Set location
172+
Object.defineProperty(global, 'location', {
173+
value: { ...dom.window.location, pathname: '/blog' },
174+
writable: true,
175+
});
176+
177+
// Call the function (should not throw)
178+
expect(() => removeIsrSsgTraceMetaTags()).not.toThrow();
179+
180+
// Verify meta tags were NOT removed (invalid manifest means no ISR detection)
181+
expect(dom.window.document.querySelector('meta[name="sentry-trace"]')).not.toBeNull();
182+
});
183+
184+
it('should handle manifest with no ISR routes', () => {
185+
// Set up DOM with meta tags
186+
const sentryTraceMeta = dom.window.document.createElement('meta');
187+
sentryTraceMeta.setAttribute('name', 'sentry-trace');
188+
sentryTraceMeta.setAttribute('content', 'trace-id-12345');
189+
dom.window.document.head.appendChild(sentryTraceMeta);
190+
191+
// Set up manifest with no ISR routes
192+
const manifestWithNoISR: RouteManifest = {
193+
staticRoutes: [{ path: '/' }],
194+
dynamicRoutes: [],
195+
isrRoutes: [],
196+
};
197+
globalWithInjectedValues._sentryRouteManifest = JSON.stringify(manifestWithNoISR);
198+
199+
// Set location
200+
Object.defineProperty(global, 'location', {
201+
value: { ...dom.window.location, pathname: '/' },
202+
writable: true,
203+
});
204+
205+
// Call the function
206+
removeIsrSsgTraceMetaTags();
207+
208+
// Verify meta tags were NOT removed (no ISR routes in manifest)
209+
expect(dom.window.document.querySelector('meta[name="sentry-trace"]')).not.toBeNull();
210+
});
211+
212+
it('should handle missing meta tags gracefully', () => {
213+
// Set up DOM without meta tags
214+
215+
// Set up route manifest
216+
globalWithInjectedValues._sentryRouteManifest = JSON.stringify(mockManifest);
217+
218+
// Set location to an ISR route
219+
Object.defineProperty(global, 'location', {
220+
value: { ...dom.window.location, pathname: '/blog' },
221+
writable: true,
222+
});
223+
224+
// Call the function (should not throw)
225+
expect(() => removeIsrSsgTraceMetaTags()).not.toThrow();
226+
227+
// Verify no errors and still no meta tags
228+
expect(dom.window.document.querySelector('meta[name="sentry-trace"]')).toBeNull();
229+
expect(dom.window.document.querySelector('meta[name="baggage"]')).toBeNull();
230+
});
231+
232+
it('should work with parameterized dynamic routes', () => {
233+
// Set up DOM with meta tags
234+
const sentryTraceMeta = dom.window.document.createElement('meta');
235+
sentryTraceMeta.setAttribute('name', 'sentry-trace');
236+
sentryTraceMeta.setAttribute('content', 'trace-id-12345');
237+
dom.window.document.head.appendChild(sentryTraceMeta);
238+
239+
const baggageMeta = dom.window.document.createElement('meta');
240+
baggageMeta.setAttribute('name', 'baggage');
241+
baggageMeta.setAttribute('content', 'sentry-trace-id=12345');
242+
dom.window.document.head.appendChild(baggageMeta);
243+
244+
// Set up route manifest
245+
globalWithInjectedValues._sentryRouteManifest = JSON.stringify(mockManifest);
246+
247+
// Set location to a different dynamic ISR route value
248+
Object.defineProperty(global, 'location', {
249+
value: { ...dom.window.location, pathname: '/posts/my-awesome-post' },
250+
writable: true,
251+
});
252+
253+
// Call the function
254+
removeIsrSsgTraceMetaTags();
255+
256+
// Verify meta tags were removed (should match /posts/:slug)
257+
expect(dom.window.document.querySelector('meta[name="sentry-trace"]')).toBeNull();
258+
expect(dom.window.document.querySelector('meta[name="baggage"]')).toBeNull();
259+
});
260+
});
261+
});
262+

0 commit comments

Comments
 (0)