Skip to content

Commit be1e9f3

Browse files
committed
Merge remote-tracking branch 'origin/master' into lforst-app-dir-browser
2 parents 4c3e556 + 2f9ab36 commit be1e9f3

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

41 files changed

+337
-88
lines changed

.github/workflows/build.yml

-1
Original file line numberDiff line numberDiff line change
@@ -191,7 +191,6 @@ jobs:
191191
- name: Check build cache
192192
uses: actions/cache@v3
193193
id: cache_built_packages
194-
if: needs.job_get_metadata.outputs.force_skip_cache == 'false'
195194
with:
196195
path: ${{ env.CACHED_BUILD_PATHS }}
197196
key: ${{ env.BUILD_CACHE_KEY }}

.size-limit.js

+6
Original file line numberDiff line numberDiff line change
@@ -78,4 +78,10 @@ module.exports = [
7878
limit: '48 KB',
7979
ignore: ['@sentry/browser', '@sentry/utils', '@sentry/core', '@sentry/types'],
8080
},
81+
{
82+
name: '@sentry/browser + @sentry/tracing + @sentry/replay - ES6 CDN Bundle (gzipped + minified)',
83+
path: 'packages/tracing/build/bundles/bundle.tracing.replay.min.js',
84+
gzip: true,
85+
limit: '80 KB',
86+
},
8187
];

CHANGELOG.md

+8
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,14 @@
44

55
- "You miss 100 percent of the chances you don't take. — Wayne Gretzky" — Michael Scott
66

7+
## 7.31.1
8+
9+
- build(replay): Provide full browser+tracing+replay bundle (#6793)
10+
- feat(nextjs): Disable NextJS perf monitoring when using otel (#6820)
11+
- fix(nextjs): Add back browser field in package.json (#6809)
12+
- fix(nextjs): Connect Edge API route errors to span (#6806)
13+
- fix(nextjs): Correctly handle ts middleware files (#6816)
14+
715
## 7.31.0
816

917
The Next.js SDK now supports error and performance monitoring for Next.js [middleware](https://nextjs.org/docs/advanced-features/middleware) and [Edge API routes](https://nextjs.org/docs/api-routes/edge-api-routes).

packages/integration-tests/suites/replay/captureReplay/test.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@ import { sentryTest } from '../../../utils/fixtures';
66
import { getFirstSentryEnvelopeRequest } from '../../../utils/helpers';
77

88
sentryTest('captureReplay', async ({ getLocalTestPath, page }) => {
9-
// Currently bundle tests are not supported for replay
10-
if (process.env.PW_BUNDLE && process.env.PW_BUNDLE.startsWith('bundle_')) {
9+
// Replay bundles are es6 only
10+
if (process.env.PW_BUNDLE && process.env.PW_BUNDLE.startsWith('bundle_es5')) {
1111
sentryTest.skip();
1212
}
1313

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import * as Sentry from '@sentry/browser';
2+
3+
window.Sentry = Sentry;
4+
window.Replay = new Sentry.Replay({
5+
flushMinDelay: 200,
6+
initialFlushDelay: 200,
7+
});
8+
9+
Sentry.init({
10+
dsn: 'https://[email protected]/1337',
11+
sampleRate: 0,
12+
replaysSessionSampleRate: 1.0,
13+
replaysOnErrorSampleRate: 0.0,
14+
15+
integrations: [window.Replay],
16+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<!DOCTYPE html>
2+
<html>
3+
<head>
4+
<meta charset="utf-8" />
5+
</head>
6+
<body>
7+
<button onclick="console.log('Test log')">Click me</button>
8+
</body>
9+
</html>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
import { expect } from '@playwright/test';
2+
import { SDK_VERSION } from '@sentry/browser';
3+
import type { Event } from '@sentry/types';
4+
5+
import { sentryTest } from '../../../utils/fixtures';
6+
import { getFirstSentryEnvelopeRequest } from '../../../utils/helpers';
7+
8+
sentryTest('captureReplay', async ({ getLocalTestPath, page }) => {
9+
// For this test, we skip all bundle tests, as we're only interested in Replay being correctly
10+
// exported from the `@sentry/browser` npm package.
11+
if (process.env.PW_BUNDLE && process.env.PW_BUNDLE.startsWith('bundle_')) {
12+
sentryTest.skip();
13+
}
14+
15+
await page.route('https://dsn.ingest.sentry.io/**/*', route => {
16+
return route.fulfill({
17+
status: 200,
18+
contentType: 'application/json',
19+
body: JSON.stringify({ id: 'test-id' }),
20+
});
21+
});
22+
23+
const url = await getLocalTestPath({ testDir: __dirname });
24+
await page.goto(url);
25+
26+
await page.click('button');
27+
await page.waitForTimeout(300);
28+
29+
const replayEvent = await getFirstSentryEnvelopeRequest<Event>(page, url);
30+
31+
expect(replayEvent).toBeDefined();
32+
expect(replayEvent).toEqual({
33+
type: 'replay_event',
34+
timestamp: expect.any(Number),
35+
error_ids: [],
36+
trace_ids: [],
37+
urls: [expect.stringContaining('/dist/index.html')],
38+
replay_id: expect.stringMatching(/\w{32}/),
39+
segment_id: 2,
40+
replay_type: 'session',
41+
event_id: expect.stringMatching(/\w{32}/),
42+
environment: 'production',
43+
sdk: {
44+
integrations: [
45+
'InboundFilters',
46+
'FunctionToString',
47+
'TryCatch',
48+
'Breadcrumbs',
49+
'GlobalHandlers',
50+
'LinkedErrors',
51+
'Dedupe',
52+
'HttpContext',
53+
'Replay',
54+
],
55+
version: SDK_VERSION,
56+
name: 'sentry.javascript.browser',
57+
},
58+
sdkProcessingMetadata: {},
59+
request: {
60+
url: expect.stringContaining('/dist/index.html'),
61+
headers: {
62+
'User-Agent': expect.stringContaining(''),
63+
},
64+
},
65+
platform: 'javascript',
66+
tags: { sessionSampleRate: 1, errorSampleRate: 0 },
67+
});
68+
});

packages/integration-tests/suites/replay/errorResponse/test.ts

+4-2
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { getReplaySnapshot } from '../../../utils/helpers';
55

66
sentryTest('errorResponse', async ({ getLocalTestPath, page }) => {
77
// Currently bundle tests are not supported for replay
8-
if (process.env.PW_BUNDLE && process.env.PW_BUNDLE.startsWith('bundle_')) {
8+
if (process.env.PW_BUNDLE && process.env.PW_BUNDLE.startsWith('bundle_es5')) {
99
sentryTest.skip();
1010
}
1111

@@ -31,7 +31,9 @@ sentryTest('errorResponse', async ({ getLocalTestPath, page }) => {
3131
await page.waitForTimeout(5001);
3232

3333
expect(called).toBe(1);
34+
3435
const replay = await getReplaySnapshot(page);
3536

36-
expect(replay['_isEnabled']).toBe(false);
37+
// @ts-ignore private API
38+
expect(replay._isEnabled).toBe(false);
3739
});

packages/integration-tests/suites/replay/init.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import * as Sentry from '@sentry/browser';
2+
import { Replay } from '@sentry/replay';
23

34
window.Sentry = Sentry;
4-
window.Replay = new Sentry.Replay({
5+
window.Replay = new Replay({
56
flushMinDelay: 200,
67
initialFlushDelay: 200,
78
});

packages/integration-tests/suites/replay/sampling/init.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import * as Sentry from '@sentry/browser';
2+
import { Replay } from '@sentry/replay';
23

34
window.Sentry = Sentry;
4-
window.Replay = new Sentry.Replay({
5+
window.Replay = new Replay({
56
flushMinDelay: 200,
67
initialFlushDelay: 200,
78
});

packages/integration-tests/suites/replay/sampling/test.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@ import { sentryTest } from '../../../utils/fixtures';
44
import { getReplaySnapshot } from '../../../utils/helpers';
55

66
sentryTest('sampling', async ({ getLocalTestPath, page }) => {
7-
// Currently bundle tests are not supported for replay
8-
if (process.env.PW_BUNDLE && process.env.PW_BUNDLE.startsWith('bundle_')) {
7+
// Replay bundles are es6 only
8+
if (process.env.PW_BUNDLE && process.env.PW_BUNDLE.startsWith('bundle_es5')) {
99
sentryTest.skip();
1010
}
1111

packages/integration-tests/utils/generatePlugin.ts

+18
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,12 @@ const BUNDLE_PATHS: Record<string, Record<string, string>> = {
4040
bundle_es6: 'build/bundles/[INTEGRATION_NAME].js',
4141
bundle_es6_min: 'build/bundles/[INTEGRATION_NAME].min.js',
4242
},
43+
replay: {
44+
cjs: 'build/npm/cjs/index.js',
45+
esm: 'build/npm/esm/index.js',
46+
bundle_es6: 'build/bundles/replay.js',
47+
bundle_es6_min: 'build/bundles/replay.min.js',
48+
},
4349
};
4450

4551
/*
@@ -87,6 +93,7 @@ function generateSentryAlias(): Record<string, string> {
8793
class SentryScenarioGenerationPlugin {
8894
public requiresTracing: boolean = false;
8995
public requiredIntegrations: string[] = [];
96+
public requiresReplay = false;
9097

9198
private _name: string = 'SentryScenarioGenerationPlugin';
9299

@@ -99,6 +106,7 @@ class SentryScenarioGenerationPlugin {
99106
'@sentry/browser': 'Sentry',
100107
'@sentry/tracing': 'Sentry',
101108
'@sentry/integrations': 'Sentry.Integrations',
109+
'@sentry/replay': 'Sentry.Integrations',
102110
}
103111
: {};
104112

@@ -113,6 +121,8 @@ class SentryScenarioGenerationPlugin {
113121
this.requiresTracing = true;
114122
} else if (source === '@sentry/integrations') {
115123
this.requiredIntegrations.push(statement.specifiers[0].imported.name.toLowerCase());
124+
} else if (source === '@sentry/replay') {
125+
this.requiresReplay = true;
116126
}
117127
},
118128
);
@@ -140,6 +150,14 @@ class SentryScenarioGenerationPlugin {
140150
data.assetTags.scripts.unshift(integrationObject);
141151
});
142152

153+
if (this.requiresReplay && BUNDLE_PATHS['replay'][bundleKey]) {
154+
const replayObject = createHtmlTagObject('script', {
155+
src: path.resolve(PACKAGES_DIR, 'replay', BUNDLE_PATHS['replay'][bundleKey]),
156+
});
157+
158+
data.assetTags.scripts.unshift(replayObject);
159+
}
160+
143161
data.assetTags.scripts.unshift(bundleObject);
144162
}
145163

packages/nextjs/package.json

+4-3
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,9 @@
99
"engines": {
1010
"node": ">=8"
1111
},
12-
"main": "build/cjs/index.js",
13-
"module": "build/esm/index.js",
12+
"main": "build/cjs/index.server.js",
13+
"module": "build/esm/index.server.js",
14+
"browser": "build/esm/index.client.js",
1415
"types": "build/types/index.types.d.ts",
1516
"publishConfig": {
1617
"access": "public"
@@ -54,7 +55,7 @@
5455
"build:transpile:watch": "nodemon --ext ts --watch src scripts/buildRollup.ts",
5556
"build:types:watch": "tsc -p tsconfig.types.json --watch",
5657
"build:tarball": "ts-node ../../scripts/prepack.ts && npm pack ./build",
57-
"circularDepCheck": "madge --circular src/index.ts && madge --circular src/client/index.ts && madge --circular src/index.types.ts",
58+
"circularDepCheck": "madge --circular src/index.client.ts && madge --circular src/edge/index.ts && madge --circular src/index.server.ts && madge --circular src/index.types.ts",
5859
"clean": "rimraf build coverage sentry-nextjs-*.tgz",
5960
"fix": "run-s fix:eslint fix:prettier",
6061
"fix:eslint": "eslint . --format stylish --fix",

packages/nextjs/rollup.npm.config.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ export default [
55
makeBaseNPMConfig({
66
// We need to include `instrumentServer.ts` separately because it's only conditionally required, and so rollup
77
// doesn't automatically include it when calculating the module dependency tree.
8-
entrypoints: ['src/index.ts', 'src/client/index.ts', 'src/edge/index.ts', 'src/config/webpack.ts'],
8+
entrypoints: ['src/index.server.ts', 'src/index.client.ts', 'src/edge/index.ts', 'src/config/webpack.ts'],
99

1010
// prevent this internal nextjs code from ending up in our built package (this doesn't happen automatially because
1111
// the name doesn't match an SDK dependency)

packages/nextjs/src/config/loaders/wrappingLoader.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ export default function wrappingLoader(
6868
}
6969

7070
const middlewareJsPath = path.join(pagesDir, '..', 'middleware.js');
71-
const middlewareTsPath = path.join(pagesDir, '..', 'middleware.js');
71+
const middlewareTsPath = path.join(pagesDir, '..', 'middleware.ts');
7272

7373
let templateCode: string;
7474
if (parameterizedRoute.startsWith('/api')) {

packages/nextjs/src/edge/utils/edgeWrapperUtils.ts

+1
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ export function withEdgeWrapping<H extends EdgeRouteHandler>(
8080
span?.setStatus('internal_error');
8181

8282
captureException(objectifiedErr, scope => {
83+
scope.setSpan(span);
8384
scope.addEventProcessor(event => {
8485
addExceptionMechanism(event, {
8586
type: 'instrument',

packages/nextjs/src/index.client.ts

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
export * from './client';
2+
3+
// This file is the main entrypoint for non-Next.js build pipelines that use
4+
// the package.json's "browser" field or the Edge runtime (Edge API routes and middleware)
5+
6+
// __SENTRY_SDK_MULTIPLEXER__

packages/nextjs/src/index.server.ts

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
export * from './config';
2+
export * from './server';
3+
4+
// This file is the main entrypoint on the server and/or when the package is `require`d
5+
6+
// __SENTRY_SDK_MULTIPLEXER__

packages/nextjs/src/index.ts

-4
This file was deleted.

packages/nextjs/src/server/wrapApiHandlerWithSentry.ts

+4-3
Original file line numberDiff line numberDiff line change
@@ -87,12 +87,14 @@ export function withSentry(origHandler: NextApiHandler, parameterizedRoute?: str
8787
// eslint-disable-next-line complexity
8888
const boundHandler = local.bind(async () => {
8989
let transaction: Transaction | undefined;
90-
const currentScope = getCurrentHub().getScope();
90+
const hub = getCurrentHub();
91+
const currentScope = hub.getScope();
92+
const options = hub.getClient()?.getOptions();
9193

9294
if (currentScope) {
9395
currentScope.setSDKProcessingMetadata({ request: req });
9496

95-
if (hasTracingEnabled()) {
97+
if (hasTracingEnabled(options) && options?.instrumenter === 'sentry') {
9698
// If there is a trace header set, extract the data from it (parentSpanId, traceId, and sampling decision)
9799
let traceparentData;
98100
if (req.headers && isString(req.headers['sentry-trace'])) {
@@ -137,7 +139,6 @@ export function withSentry(origHandler: NextApiHandler, parameterizedRoute?: str
137139
{ request: req },
138140
);
139141
currentScope.setSpan(transaction);
140-
141142
if (platformSupportsStreaming() && !origHandler.__sentry_test_doesnt_support_streaming__) {
142143
autoEndTransactionOnResponseEnd(transaction, res);
143144
} else {

packages/nextjs/src/server/wrapAppGetInitialPropsWithSentry.ts

+3-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { getCurrentHub } from '@sentry/node';
12
import { hasTracingEnabled } from '@sentry/tracing';
23
import { dynamicSamplingContextToSentryBaggageHeader } from '@sentry/utils';
34
import type App from 'next/app';
@@ -29,12 +30,13 @@ export function wrapAppGetInitialPropsWithSentry(origAppGetInitialProps: AppGetI
2930
const { req, res } = context.ctx;
3031

3132
const errorWrappedAppGetInitialProps = withErrorInstrumentation(origAppGetInitialProps);
33+
const options = getCurrentHub().getClient()?.getOptions();
3234

3335
// Generally we can assume that `req` and `res` are always defined on the server:
3436
// https://nextjs.org/docs/api-reference/data-fetching/get-initial-props#context-object
3537
// This does not seem to be the case in dev mode. Because we have no clean way of associating the the data fetcher
3638
// span with each other when there are no req or res objects, we simply do not trace them at all here.
37-
if (hasTracingEnabled() && req && res) {
39+
if (hasTracingEnabled() && req && res && options?.instrumenter === 'sentry') {
3840
const tracedGetInitialProps = withTracedServerSideDataFetcher(errorWrappedAppGetInitialProps, req, res, {
3941
dataFetcherRouteName: '/_app',
4042
requestedRouteName: context.ctx.pathname,

packages/nextjs/src/server/wrapDocumentGetInitialPropsWithSentry.ts

+3-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { getCurrentHub } from '@sentry/node';
12
import { hasTracingEnabled } from '@sentry/tracing';
23
import type Document from 'next/document';
34

@@ -29,12 +30,13 @@ export function wrapDocumentGetInitialPropsWithSentry(
2930
const { req, res } = context;
3031

3132
const errorWrappedGetInitialProps = withErrorInstrumentation(origDocumentGetInitialProps);
33+
const options = getCurrentHub().getClient()?.getOptions();
3234

3335
// Generally we can assume that `req` and `res` are always defined on the server:
3436
// https://nextjs.org/docs/api-reference/data-fetching/get-initial-props#context-object
3537
// This does not seem to be the case in dev mode. Because we have no clean way of associating the the data fetcher
3638
// span with each other when there are no req or res objects, we simply do not trace them at all here.
37-
if (hasTracingEnabled() && req && res) {
39+
if (hasTracingEnabled() && req && res && options?.instrumenter === 'sentry') {
3840
const tracedGetInitialProps = withTracedServerSideDataFetcher(errorWrappedGetInitialProps, req, res, {
3941
dataFetcherRouteName: '/_document',
4042
requestedRouteName: context.pathname,

packages/nextjs/src/server/wrapErrorGetInitialPropsWithSentry.ts

+3-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { getCurrentHub } from '@sentry/node';
12
import { hasTracingEnabled } from '@sentry/tracing';
23
import { dynamicSamplingContextToSentryBaggageHeader } from '@sentry/utils';
34
import type { NextPageContext } from 'next';
@@ -32,12 +33,13 @@ export function wrapErrorGetInitialPropsWithSentry(
3233
const { req, res } = context;
3334

3435
const errorWrappedGetInitialProps = withErrorInstrumentation(origErrorGetInitialProps);
36+
const options = getCurrentHub().getClient()?.getOptions();
3537

3638
// Generally we can assume that `req` and `res` are always defined on the server:
3739
// https://nextjs.org/docs/api-reference/data-fetching/get-initial-props#context-object
3840
// This does not seem to be the case in dev mode. Because we have no clean way of associating the the data fetcher
3941
// span with each other when there are no req or res objects, we simply do not trace them at all here.
40-
if (hasTracingEnabled() && req && res) {
42+
if (hasTracingEnabled() && req && res && options?.instrumenter === 'sentry') {
4143
const tracedGetInitialProps = withTracedServerSideDataFetcher(errorWrappedGetInitialProps, req, res, {
4244
dataFetcherRouteName: '/_error',
4345
requestedRouteName: context.pathname,

0 commit comments

Comments
 (0)