Skip to content

Commit f60aae5

Browse files
s1gr1dAbhiPrasad
andauthored
feat(nuxt): Configure sentry in external config (#12681)
To be able to differentiate between a browser/client execution context, sentry is initialized in an external config file. An import statement in `nuxt-root.vue` is added which loads this config file. Nuxt tracking issue: #9095 --------- Co-authored-by: Abhijeet Prasad <aprasad@sentry.io>
1 parent f4a289d commit f60aae5

File tree

12 files changed

+253
-43
lines changed

12 files changed

+253
-43
lines changed

packages/nuxt/README.md

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -49,27 +49,33 @@ If the setup through the wizard doesn't work for you, you can also set up the SD
4949
yarn add @sentry/nuxt
5050
```
5151

52-
### 2. Client-side Setup
52+
### 2. Nuxt Module Setup
5353

5454
The Sentry Nuxt SDK is based on [Nuxt Modules](https://nuxt.com/docs/api/kit/modules).
5555

56-
1. Add `@sentry/nuxt` to the modules section of `nuxt.config.ts`:
56+
1. Add `@sentry/nuxt/module` to the modules section of `nuxt.config.ts`:
5757

5858
```javascript
5959
// nuxt.config.ts
6060
export default defineNuxtConfig({
61-
modules: ['@sentry/nuxt'],
62-
runtimeConfig: {
63-
public: {
64-
sentry: {
65-
dsn: env.DSN,
66-
// Additional config
67-
},
68-
},
69-
},
61+
modules: ['@sentry/nuxt/module'],
7062
});
7163
```
7264

65+
2. Add a `sentry.client.config.(js|ts)` file to the root of your project:
66+
67+
```javascript
68+
import * as Sentry from '@sentry/nuxt';
69+
70+
if (!import.meta.env.SSR) {
71+
Sentry.init({
72+
dsn: env.DSN,
73+
replaysSessionSampleRate: 0.1,
74+
replaysOnErrorSampleRate: 1.0,
75+
});
76+
}
77+
```
78+
7379
### 3. Server-side Setup
7480
7581
todo: add server-side setup

packages/nuxt/package.json

Lines changed: 27 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -12,16 +12,28 @@
1212
"files": [
1313
"/build"
1414
],
15-
"main": "build/module.cjs",
16-
"module": "build/module.mjs",
17-
"types": "build/types.d.ts",
15+
"main": "build/cjs/index.server.js",
16+
"module": "build/esm/index.server.js",
17+
"browser": "build/esm/index.client.js",
18+
"types": "build/types/index.types.d.ts",
1819
"exports": {
20+
"./package.json": "./package.json",
1921
".": {
20-
"types": "./build/types.d.ts",
21-
"import": "./build/module.mjs",
22-
"require": "./build/module.cjs"
22+
"types": "./build/types/index.types.d.ts",
23+
"browser": {
24+
"import": "./build/esm/index.client.js",
25+
"require": "./build/cjs/index.client.js"
26+
},
27+
"node": {
28+
"import": "./build/esm/index.server.js",
29+
"require": "./build/cjs/index.server.js"
30+
}
2331
},
24-
"./package.json": "./package.json"
32+
"./module": {
33+
"types": "./build/module/types.d.ts",
34+
"import": "./build/module/module.mjs",
35+
"require": "./build/module/module.cjs"
36+
}
2537
},
2638
"publishConfig": {
2739
"access": "public"
@@ -31,6 +43,7 @@
3143
},
3244
"dependencies": {
3345
"@nuxt/kit": "^3.12.2",
46+
"@sentry/browser": "8.13.0",
3447
"@sentry/core": "8.13.0",
3548
"@sentry/node": "8.13.0",
3649
"@sentry/opentelemetry": "8.13.0",
@@ -44,12 +57,14 @@
4457
"nuxt": "^3.12.2"
4558
},
4659
"scripts": {
47-
"build": "run-p build:transpile",
60+
"build": "run-p build:transpile build:types build:nuxt-module",
4861
"build:dev": "yarn build",
49-
"build:transpile": "nuxt-module-build build --outDir build",
62+
"build:nuxt-module": "nuxt-module-build build --outDir build/module",
63+
"build:transpile": "rollup -c rollup.npm.config.mjs",
64+
"build:types": "tsc -p tsconfig.types.json",
5065
"build:watch": "run-p build:transpile:watch build:types:watch",
5166
"build:dev:watch": "yarn build:watch",
52-
"build:transpile:watch": "nuxt-module-build build --outDir build --watch",
67+
"build:transpile:watch": "rollup -c rollup.npm.config.mjs --watch",
5368
"build:types:watch": "tsc -p tsconfig.types.json --watch",
5469
"build:tarball": "npm pack",
5570
"circularDepCheck": "madge --circular src/index.client.ts && madge --circular src/index.server.ts && madge --circular src/index.types.ts",
@@ -72,7 +87,8 @@
7287
"^build:types"
7388
],
7489
"outputs": [
75-
"{projectRoot}/build"
90+
"{projectRoot}/build",
91+
"{projectRoot}/build/module"
7692
]
7793
}
7894
}

packages/nuxt/rollup.npm.config.mjs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import { makeBaseNPMConfig, makeNPMConfigVariants } from '@sentry-internal/rollup-utils';
2+
3+
export default makeNPMConfigVariants(
4+
makeBaseNPMConfig({
5+
entrypoints: ['src/index.client.ts', 'src/client/index.ts'],
6+
}),
7+
);

packages/nuxt/src/client/sdk.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,19 @@
1+
import { init as initBrowser } from '@sentry/browser';
12
import { applySdkMetadata } from '@sentry/core';
23
import type { Client } from '@sentry/types';
3-
import { init as initVue } from '@sentry/vue';
4-
import type { SentryVueOptions } from '../common/types';
4+
import type { SentryNuxtOptions } from '../common/types';
55

66
/**
77
* Initializes the client-side of the Nuxt SDK
88
*
99
* @param options Configuration options for the SDK.
1010
*/
11-
export function init(options: SentryVueOptions): Client | undefined {
11+
export function init(options: SentryNuxtOptions): Client | undefined {
1212
const sentryOptions = {
1313
...options,
1414
};
1515

1616
applySdkMetadata(sentryOptions, 'nuxt', ['nuxt', 'vue']);
1717

18-
return initVue(sentryOptions);
18+
return initBrowser(sentryOptions);
1919
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
declare const __DEBUG_BUILD__: boolean;
2+
3+
/**
4+
* This serves as a build time flag that will be true by default, but false in non-debug builds or if users replace `__SENTRY_DEBUG__` in their generated code.
5+
*
6+
* ATTENTION: This constant must never cross package boundaries (i.e. be exported) to guarantee that it can be used for tree shaking.
7+
*/
8+
export const DEBUG_BUILD = __DEBUG_BUILD__;

packages/nuxt/src/common/snippets.ts

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
import * as fs from 'fs';
2+
import * as path from 'path';
3+
4+
/** Returns an import snippet */
5+
export function buildSdkInitFileImportSnippet(filePath: string): string {
6+
const posixPath = filePath.split(path.sep).join(path.posix.sep);
7+
8+
// normalize to forward slashed for Windows-based systems
9+
const normalizedPath = posixPath.replace(/\\/g, '/');
10+
11+
return `import '${normalizedPath}';`;
12+
}
13+
14+
/**
15+
* Script tag inside `nuxt-root.vue` (root component we get from NuxtApp)
16+
*/
17+
export const SCRIPT_TAG = '<script setup>';
18+
19+
/**
20+
* Adds a top-level import statement right after <script setup>.
21+
* This should happen as early as possible (e.g. in root component)
22+
*/
23+
export function addImportStatement(filePath: string, importStatement: string): void {
24+
try {
25+
const data = fs.readFileSync(filePath, 'utf8');
26+
const scriptIndex = data.indexOf(SCRIPT_TAG);
27+
28+
if (scriptIndex === -1) {
29+
// eslint-disable-next-line no-console
30+
console.warn(`[Sentry] Sentry not initialized. Could not find ${SCRIPT_TAG} in ${filePath}`);
31+
return;
32+
}
33+
34+
// Insert the import statement after the script tag
35+
const output = data.replace(SCRIPT_TAG, `${SCRIPT_TAG}\n${importStatement}\n`);
36+
37+
try {
38+
fs.writeFileSync(filePath, output, 'utf8');
39+
} catch (err) {
40+
// eslint-disable-next-line no-console
41+
console.error(`[Sentry] Error writing file to ${filePath}: ${err}`);
42+
}
43+
} catch (err) {
44+
// eslint-disable-next-line no-console
45+
console.error(`[Sentry] Error reading file at ${filePath}: ${err}`);
46+
}
47+
}

packages/nuxt/src/common/types.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
import type { init } from '@sentry/vue';
22

3-
export type SentryVueOptions = Parameters<typeof init>[0] & object;
3+
// Omitting 'app' as the Nuxt SDK will add the app instance in the client plugin (users do not have to provide this)
4+
export type SentryNuxtOptions = Omit<Parameters<typeof init>[0] & object, 'app'>;

packages/nuxt/src/index.types.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
export {};
1+
export * from './client';

packages/nuxt/src/module.ts

Lines changed: 32 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,49 @@
1+
import * as fs from 'fs';
2+
import * as path from 'path';
13
import { type Resolver, addPlugin, createResolver, defineNuxtModule } from '@nuxt/kit';
2-
import type { SentryVueOptions } from './common/types';
4+
import { addImportStatement, buildSdkInitFileImportSnippet } from './common/snippets';
5+
import type { SentryNuxtOptions } from './common/types';
36

4-
export type ModuleOptions = SentryVueOptions;
7+
export type ModuleOptions = SentryNuxtOptions;
58

69
export default defineNuxtModule<ModuleOptions>({
710
meta: {
8-
name: '@sentry/nuxt',
11+
name: '@sentry/nuxt/module',
912
configKey: 'sentry',
1013
compatibility: {
1114
nuxt: '^3.0.0',
1215
},
1316
},
14-
// Default configuration options of the Nuxt module
1517
defaults: {},
16-
setup(_moduleOptions, _nuxt) {
18+
setup(_moduleOptions, nuxt) {
1719
const resolver: Resolver = createResolver(import.meta.url);
1820

21+
const pathToClientInit = findDefaultSdkInitFile('client');
22+
23+
if (pathToClientInit) {
24+
nuxt.hook('app:templates', nuxtApp => {
25+
if (nuxtApp.rootComponent) {
26+
try {
27+
addImportStatement(nuxtApp.rootComponent, buildSdkInitFileImportSnippet(pathToClientInit));
28+
} catch (err) {
29+
// eslint-disable-next-line no-console
30+
console.error(`[Sentry] Could not add import statement to root component. ${err}`);
31+
}
32+
}
33+
});
34+
}
35+
1936
if (resolver) {
20-
addPlugin(resolver.resolve('runtime/plugins/sentry.client.js'));
37+
addPlugin(resolver.resolve('./runtime/plugins/sentry.client'));
2138
}
2239
},
2340
});
41+
42+
function findDefaultSdkInitFile(type: /* 'server' | */ 'client'): string | undefined {
43+
const possibleFileExtensions = ['ts', 'js', 'mjs', 'cjs', 'mts', 'cts'];
44+
45+
const cwd = process.cwd();
46+
return possibleFileExtensions
47+
.map(e => path.resolve(path.join(cwd, `sentry.${type}.config.${e}`)))
48+
.find(filename => fs.existsSync(filename));
49+
}
Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
1-
import { defineNuxtPlugin, useRuntimeConfig } from 'nuxt/app';
2-
import { init } from '../../client';
1+
import { getClient } from '@sentry/core';
2+
import { vueIntegration } from '@sentry/vue';
3+
import { defineNuxtPlugin } from 'nuxt/app';
34

45
export default defineNuxtPlugin(nuxtApp => {
5-
const config = useRuntimeConfig();
6-
const sentryConfig = config.public.sentry || {};
6+
nuxtApp.hook('app:created', vueApp => {
7+
const sentryClient = getClient();
78

8-
init({
9-
...sentryConfig,
10-
app: nuxtApp.vueApp,
9+
if (sentryClient) {
10+
sentryClient.addIntegration(vueIntegration({ app: vueApp }));
11+
}
1112
});
1213
});

packages/nuxt/test/client/sdk.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
1+
import * as SentryBrowser from '@sentry/browser';
12
import { SDK_VERSION } from '@sentry/vue';
2-
import * as SentryVue from '@sentry/vue';
33
import { beforeEach, describe, expect, it, vi } from 'vitest';
44
import { init } from '../../src/client';
55

6-
const vueInit = vi.spyOn(SentryVue, 'init');
6+
const vueInit = vi.spyOn(SentryBrowser, 'init');
77

88
describe('Nuxt Client SDK', () => {
99
describe('init', () => {

0 commit comments

Comments
 (0)