Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
688bde0
feat: init nitro SDK
logaretm Dec 31, 2025
109048e
chore: mark as private for now
logaretm Feb 6, 2026
9752a6e
chore: allow publish
logaretm Feb 9, 2026
7fa5a29
chore: dedup yarn
logaretm Feb 10, 2026
bc55f26
fix: satisfy the test runner tests
logaretm Feb 10, 2026
4ae2b1d
fix: lint
logaretm Feb 10, 2026
24a438a
fix: dedup
logaretm Feb 23, 2026
6cf301b
chore: bump
logaretm Feb 23, 2026
c6d4918
chore: dedup deps
logaretm Apr 15, 2026
0454a21
chore: update other metadata
logaretm Apr 15, 2026
f385a0f
chore: address PR review feedback for nitro package
logaretm Apr 16, 2026
bc68aff
chore(nitro): remove leftover eslintrc config
logaretm Apr 16, 2026
5ac385e
chore: update packages
logaretm Apr 16, 2026
3ec90ba
feat(nitro): Instrument HTTP Server (#19225)
logaretm Apr 17, 2026
3c1d1ce
feat(nitro): Handle sourcemap preparation and upload (#19304)
logaretm Apr 22, 2026
5fdd0df
chore(nitro): switch lint scripts to oxlint
logaretm Apr 23, 2026
dcda795
chore(nitro): drop cjs build and clean up package exports
logaretm Apr 23, 2026
bb4b1e7
fix: updated the readme with the correct min version
logaretm Apr 23, 2026
2d31add
fix(nitro): only compute default integrations when not provided
logaretm Apr 23, 2026
a62ff3c
chore: update package versions
logaretm Apr 23, 2026
e942199
fix(nitro): use parseUrl to handle relative request URLs in error hook
logaretm Apr 23, 2026
2c52283
test(nitro): use packed tarballs instead of verdaccio registry
logaretm Apr 23, 2026
a2bf6f6
test(nitro): revert dep specifier changes, rely on CI pnpm.overrides
logaretm Apr 23, 2026
6869c2a
chore(nitro): add release metadata and config
logaretm Apr 23, 2026
2e68a40
chore(nitro): clean up stale tsconfig.types.json references
logaretm Apr 23, 2026
000b7b0
fix: format
logaretm Apr 23, 2026
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
9 changes: 9 additions & 0 deletions .craft.yml
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,9 @@ targets:
- name: npm
id: '@sentry/react-router'
includeNames: /^sentry-react-router-\d.*\.tgz$/
- name: npm
id: '@sentry/nitro'
includeNames: /^sentry-nitro-\d.*\.tgz$/

## 7. Other Packages
## 7.1
Expand Down Expand Up @@ -256,3 +259,9 @@ targets:
packageUrl: 'https://www.npmjs.com/package/@sentry/elysia'
mainDocsUrl: 'https://docs.sentry.io/platforms/javascript/guides/elysia/'
onlyIfPresent: /^sentry-elysia-\d.*\.tgz$/
'npm:@sentry/nitro':
name: 'Sentry Nitro SDK'
sdkName: 'sentry.javascript.nitro'
packageUrl: 'https://www.npmjs.com/package/@sentry/nitro'
mainDocsUrl: 'https://docs.sentry.io/platforms/javascript/guides/nitro/'
onlyIfPresent: /^sentry-nitro-\d.*\.tgz$/
1 change: 1 addition & 0 deletions .github/ISSUE_TEMPLATE/bug.yml
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ body:
- '@sentry/google-cloud-serverless'
- '@sentry/nestjs'
- '@sentry/nextjs'
- '@sentry/nitro'
- '@sentry/nuxt'
- '@sentry/react'
- '@sentry/react-router'
Expand Down
3 changes: 3 additions & 0 deletions .github/workflows/canary.yml
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,9 @@ jobs:
- test-application: 'nestjs-microservices'
build-command: 'test:build-latest'
label: 'nestjs-microservices (latest)'
- test-application: 'nitro-3'
build-command: 'test:build-canary'
label: 'nitro-3 (canary)'
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing test:build-canary script breaks canary CI

High Severity

The canary workflow references test:build-canary as the build-command for the nitro-3 test application, but the package.json for nitro-3 only defines test:build — there is no test:build-canary script. The workflow step executes yarn ${{ matrix.build-command }}, which will fail. Other test applications like nuxt-3 and nuxt-4 correctly define test:build-canary scripts that install canary/nightly versions of their framework dependencies.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 4cd6415. Configure here.


steps:
- name: Check out current commit
Expand Down
3 changes: 3 additions & 0 deletions .github/workflows/issue-package-label.yml
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,9 @@ jobs:
"@sentry.nestjs": {
"label": "Nest.js"
},
"@sentry.nitro": {
"label": "Nitro"
},
"@sentry.nextjs": {
"label": "Next.js"
},
Expand Down
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@

## Unreleased

- **feat(nitro): Add `@sentry/nitro` SDK**

A new `@sentry/nitro` package provides first-class Sentry support for [Nitro](https://nitro.build/) applications, with HTTP handler and error instrumentation, middleware tracing, request isolation, and build-time source map uploading via `withSentryConfig`.
Read more in the [Nitro SDK docs](https://docs.sentry.io/platforms/javascript/guides/nitro/) and the [Nitro SDK readme](https://github.com/getsentry/sentry-javascript/blob/develop/packages/nitro/README.md).

## 10.50.0

### Important Changes
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ package. Please refer to the README and instructions of those SDKs for more deta
- [`@sentry/gatsby`](https://github.com/getsentry/sentry-javascript/tree/master/packages/gatsby): SDK for Gatsby
- [`@sentry/nestjs`](https://github.com/getsentry/sentry-javascript/tree/master/packages/nestjs): SDK for NestJS
- [`@sentry/nextjs`](https://github.com/getsentry/sentry-javascript/tree/master/packages/nextjs): SDK for Next.js
- [`@sentry/nitro`](https://github.com/getsentry/sentry-javascript/tree/master/packages/nitro): SDK for Nitro
- [`@sentry/remix`](https://github.com/getsentry/sentry-javascript/tree/master/packages/remix): SDK for Remix
- [`@sentry/tanstackstart-react`](https://github.com/getsentry/sentry-javascript/tree/master/packages/tanstackstart-react): SDK for TanStack Start React
- [`@sentry/aws-serverless`](https://github.com/getsentry/sentry-javascript/tree/master/packages/aws-serverless): SDK
Expand Down
11 changes: 11 additions & 0 deletions dev-packages/e2e-tests/test-applications/nitro-3/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Nitro E2E Test</title>
</head>
<body>
<h1>Nitro E2E Test App</h1>
<script type="module" src="/src/main.ts"></script>
</body>
</html>
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import * as Sentry from '@sentry/nitro';

Sentry.init({
environment: 'qa', // dynamic sampling bias to keep transactions
dsn: process.env.E2E_TEST_DSN,
tunnel: `http://localhost:3031/`, // proxy server
tracesSampleRate: 1,
});
29 changes: 29 additions & 0 deletions dev-packages/e2e-tests/test-applications/nitro-3/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
{
"name": "nitro-3",
"version": "1.0.0",
"private": true,
"type": "module",
"scripts": {
"build": "vite build",
"start": "PORT=3030 NODE_OPTIONS='--import ./instrument.mjs' node .output/server/index.mjs",
"clean": "npx rimraf node_modules pnpm-lock.yaml .output",
"test": "playwright test",
"test:build": "pnpm install && pnpm build",
"test:assert": "pnpm test"
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing test:build-canary script breaks canary CI

Medium Severity

The canary workflow specifies build-command: 'test:build-canary' for nitro-3, but the package.json only defines test:build — there is no test:build-canary script. Every other canary test application (e.g. nuxt-3, nextjs-14) explicitly defines a test:build-canary script that installs canary/nightly versions of its framework. The canary CI job will fail when it runs yarn test:build-canary and the script is not found.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 47d85ab. Configure here.

},
"dependencies": {
"@sentry/browser": "latest || *",
"@sentry/nitro": "latest || *"
},
"devDependencies": {
"@playwright/test": "~1.56.0",
"@sentry-internal/test-utils": "link:../../../test-utils",
"@sentry/core": "latest || *",
"nitro": "^3.0.260415-beta",
"rolldown": "latest",
"vite": "latest"
},
"volta": {
"extends": "../../package.json"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { getPlaywrightConfig } from '@sentry-internal/test-utils';

const config = getPlaywrightConfig({
startCommand: `pnpm start`,
});

export default config;
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { defineHandler } from 'nitro/h3';

export default defineHandler(() => {
return { status: 'ok' };
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { defineHandler } from 'nitro/h3';

export default defineHandler(() => {
throw new Error('This is a test error');
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { getDefaultIsolationScope, setTag } from '@sentry/core';
import { defineHandler } from 'nitro/h3';

export default defineHandler(() => {
setTag('my-isolated-tag', true);
// Check if the tag leaked into the default (global) isolation scope
setTag('my-global-scope-isolated-tag', getDefaultIsolationScope().getScopeData().tags['my-isolated-tag']);

throw new Error('Isolation test error');
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { startSpan } from '@sentry/nitro';
import { defineHandler } from 'nitro/h3';

export default defineHandler(() => {
startSpan({ name: 'db.select', op: 'db' }, () => {
// simulate a select query
});

startSpan({ name: 'db.insert', op: 'db' }, () => {
startSpan({ name: 'db.serialize', op: 'serialize' }, () => {
// simulate serializing data before insert
});
});

return { status: 'ok', nesting: true };
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { defineHandler } from 'nitro/h3';

export default defineHandler(event => {
const id = event.req.url;
return { id };
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { defineHandler } from 'nitro/h3';

export default defineHandler(() => {
return { status: 'ok', transaction: true };
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { defineHandler, getQuery, setResponseHeader } from 'nitro/h3';

export default defineHandler(event => {
setResponseHeader(event, 'x-sentry-test-middleware', 'executed');

const query = getQuery(event);
if (query['middleware-error'] === '1') {
throw new Error('Middleware error');
}
});
10 changes: 10 additions & 0 deletions dev-packages/e2e-tests/test-applications/nitro-3/src/main.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import * as Sentry from '@sentry/browser';

// Let's us test trace propagation
Sentry.init({
environment: 'qa',
dsn: 'https://public@dsn.ingest.sentry.io/1337',
tunnel: 'http://localhost:3031/', // proxy server
integrations: [Sentry.browserTracingIntegration()],
tracesSampleRate: 1.0,
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { startEventProxyServer } from '@sentry-internal/test-utils';

startEventProxyServer({
port: 3031,
proxyServerName: 'nitro-3',
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { expect, test } from '@playwright/test';
import { waitForError } from '@sentry-internal/test-utils';

test('Sends an error event to Sentry', async ({ request }) => {
const errorEventPromise = waitForError('nitro-3', event => {
return !event.type && !!event.exception?.values?.some(v => v.value === 'This is a test error');
});

await request.get('/api/test-error');

const errorEvent = await errorEventPromise;

// Nitro wraps thrown errors in an HTTPError with .cause, producing a chained exception
expect(errorEvent.exception?.values).toHaveLength(2);

// The innermost exception (values[0]) is the original thrown error
expect(errorEvent.exception?.values?.[0]?.type).toBe('Error');
expect(errorEvent.exception?.values?.[0]?.value).toBe('This is a test error');
expect(errorEvent.exception?.values?.[0]?.mechanism).toEqual(
expect.objectContaining({
handled: false,
type: 'auto.function.nitro.captureErrorHook',
}),
);

// The outermost exception (values[1]) is the HTTPError wrapper
expect(errorEvent.exception?.values?.[1]?.type).toBe('HTTPError');
expect(errorEvent.exception?.values?.[1]?.value).toBe('This is a test error');
});

test('Does not send 404 errors to Sentry', async ({ request }) => {
let errorReceived = false;

void waitForError('nitro-3', event => {
if (!event.type) {
errorReceived = true;
return true;
}
return false;
});

await request.get('/api/non-existent-route');

expect(errorReceived).toBe(false);
});
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Negative test never fails due to race condition

Medium Severity

The "Does not send 404 errors" test voids the waitForError promise and immediately asserts errorReceived is false after the request completes. Since there's no waiting period, even if the SDK incorrectly sent a 404 error, it likely wouldn't arrive at the proxy server before the assertion runs. This test effectively provides no confidence that 404 errors are filtered — it will always pass regardless of SDK behavior.

Fix in Cursor Fix in Web

Triggered by project rule: PR Review Guidelines for Cursor Bot

Reviewed by Cursor Bugbot for commit 4cd6415. Configure here.

Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { expect, test } from '@playwright/test';
import { waitForError, waitForTransaction } from '@sentry-internal/test-utils';

test('Isolation scope prevents tag leaking between requests', async ({ request }) => {
const transactionEventPromise = waitForTransaction('nitro-3', event => {
return event?.transaction === 'GET /api/test-isolation/:id';
});

const errorPromise = waitForError('nitro-3', event => {
return !event.type && !!event.exception?.values?.some(v => v.value === 'Isolation test error');
});

await request.get('/api/test-isolation/1').catch(() => {
// noop - route throws
});

const transactionEvent = await transactionEventPromise;
const error = await errorPromise;

// Assert that isolation scope works properly
expect(error.tags?.['my-isolated-tag']).toBe(true);
expect(error.tags?.['my-global-scope-isolated-tag']).not.toBeDefined();
expect(transactionEvent.tags?.['my-isolated-tag']).toBe(true);
expect(transactionEvent.tags?.['my-global-scope-isolated-tag']).not.toBeDefined();
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { expect, test } from '@playwright/test';
import { waitForError, waitForTransaction } from '@sentry-internal/test-utils';

test('Creates middleware spans for requests', async ({ request }) => {
const transactionEventPromise = waitForTransaction('nitro-3', event => {
return event?.transaction === 'GET /api/test-transaction';
});

const response = await request.get('/api/test-transaction');

expect(response.headers()['x-sentry-test-middleware']).toBe('executed');

const transactionEvent = await transactionEventPromise;

// h3 middleware spans have origin auto.http.nitro.h3 and op middleware.nitro
const h3MiddlewareSpans = transactionEvent.spans?.filter(
span => span.origin === 'auto.http.nitro.h3' && span.op === 'middleware.nitro',
);
expect(h3MiddlewareSpans?.length).toBeGreaterThanOrEqual(1);
});

test('Captures errors thrown in middleware with error status on span', async ({ request }) => {
const errorEventPromise = waitForError('nitro-3', event => {
return !event.type && !!event.exception?.values?.some(v => v.value === 'Middleware error');
});

const transactionEventPromise = waitForTransaction('nitro-3', event => {
return event?.transaction === 'GET /api/test-transaction' && event?.contexts?.trace?.status === 'internal_error';
});

await request.get('/api/test-transaction?middleware-error=1');

const errorEvent = await errorEventPromise;
expect(errorEvent.exception?.values?.some(v => v.value === 'Middleware error')).toBe(true);

const transactionEvent = await transactionEventPromise;

// The transaction span should have error status
expect(transactionEvent.contexts?.trace?.status).toBe('internal_error');
});
Loading
Loading