Skip to content

ref(nuxt): Extract handler patching to extra plugin for Nitro v2/v3#19915

Merged
s1gr1d merged 5 commits intodevelopfrom
sig/nuxt-v5-handler
Mar 23, 2026
Merged

ref(nuxt): Extract handler patching to extra plugin for Nitro v2/v3#19915
s1gr1d merged 5 commits intodevelopfrom
sig/nuxt-v5-handler

Conversation

@s1gr1d
Copy link
Member

@s1gr1d s1gr1d commented Mar 20, 2026

h3App is called h3 in Nitro v3. Additionally, the type imports have changed.

This PR is just moving some code around (not changing any logic). Those are the made changes:

  • Installing nitro as a devDependency -> the new import in Nitro v3 (instead of nitropack)
  • Created extra plugin that just does the handler patching (to not have to duplicate everthing
  • Extracted the patching function as an utility function which accepts a generic type (as type imports differ in Nitro versions)

Currently, the handler.server.ts file (for Nuxt v5) is unused as we don't have a reliable way yet to tell whether Nitro v2 or v3 is running. In theory we could do something like this below but this only checks for Nuxt and not Nitro.

    // Checking for compatibilityVersion 5 in Nuxt, does not mean that Nitro v3 is installed for sure.
    if (nuxt.options.future.compatibilityVersion < 5) {
      addServerPlugin(moduleDirResolver.resolve('./runtime/plugins/handler.server'));
    } else {
      addServerPlugin(moduleDirResolver.resolve('./runtime/plugins/handler-legacy.server'));
    }

Closes #19913

@s1gr1d s1gr1d requested a review from logaretm March 20, 2026 13:02
@github-actions
Copy link
Contributor

github-actions bot commented Mar 20, 2026

Semver Impact of This PR

🟢 Patch (bug fixes)

📋 Changelog Preview

This is how your changes will appear in the changelog.
Entries from this PR are highlighted with a left border (blockquote style).


New Features ✨

Deps

  • Bump mongodb-memory-server-global from 10.1.4 to 11.0.1 by dependabot in #19888
  • Bump stacktrace-parser from 0.1.10 to 0.1.11 by dependabot in #19887

Bug Fixes 🐛

Cloudflare

  • Send correct events in local development by JPeer264 in #19900
  • Forward ctx argument to Workflow.do user callback by Lms24 in #19891

Core

  • Do not overwrite user provided conversation id in Vercel by nicohrubec in #19903
  • Return same value from startSpan as callback returns by s1gr1d in #19300

Deps

  • Bump fast-xml-parser to 5.5.8 in @azure/core-xml chain by chargome in #19918
  • Bump next to 15.5.14 in nextjs-15 and nextjs-15-intl E2E test apps by chargome in #19917
  • Bump socket.io-parser to 4.2.6 to fix CVE-2026-33151 by chargome in #19880

Other

  • (craft) Add missing mainDocsUrl for @sentry/effect SDK by bc-sentry in #19860
  • (nestjs) Add node to nest metadata by chargome in #19875
  • (serverless) Add node to metadata by nicohrubec in #19878

Internal Changes 🔧

Deps Dev

  • Bump qunit-dom from 3.2.1 to 3.5.0 by dependabot in #19546
  • Bump @react-router/node from 7.13.0 to 7.13.1 by dependabot in #19544

Other

  • (astro) Re-enable server island tracing e2e test in Astro 6 by Lms24 in #19872
  • (ci) Fix "Gatbsy" typo in issue package label workflow by chargome in #19905
  • (claude) Enable Claude Code Intelligence (LSP) by s1gr1d in #19930
  • (lint) Resolve oxlint warnings by isaacs in #19893
  • (node-integration-tests) Remove unnecessary file-type dependency by Lms24 in #19824
  • (nuxt) Extract handler patching to extra plugin for Nitro v2/v3 by s1gr1d in #19915
  • (remix) Replace glob with native recursive fs walk by roli-lpci in #19531
  • (sveltekit) Replace recast + @babel/parser with acorn by roli-lpci in #19533
  • Add external contributor to CHANGELOG.md by javascript-sdk-gitflow in #19925
  • Add external contributor to CHANGELOG.md by javascript-sdk-gitflow in #19909

🤖 This preview updates automatically when you update the PR.

@s1gr1d s1gr1d force-pushed the sig/nuxt-v5-handler branch from 3844373 to 9c9528e Compare March 20, 2026 13:04
Comment on lines +83 to 84
addServerPlugin(moduleDirResolver.resolve('./runtime/plugins/handler-legacy.server'));
addServerPlugin(moduleDirResolver.resolve('./runtime/plugins/sentry.server'));
Copy link

Choose a reason for hiding this comment

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

Bug: The new handler-legacy.server.ts plugin and the existing sentry.server.ts plugin both patch nitroApp.h3App.handler, leading to the handler being wrapped twice.
Severity: MEDIUM

Suggested Fix

The refactoring appears to be incomplete. To avoid double patching, the patching logic within sentry.server.ts should be removed, as the intent was to centralize this functionality in the new handler-legacy.server.ts plugin.

Prompt for AI Agent
Review the code at the location below. A potential bug has been identified by an AI
agent.
Verify if this is a real issue. If it is, propose a fix; if not, explain why it's not
valid.

Location: packages/nuxt/src/module.ts#L83-L84

Potential issue: The pull request introduces a new server plugin,
`handler-legacy.server.ts`, which patches the Nitro event handler
`nitroApp.h3App.handler`. However, the existing `sentry.server.ts` plugin, which also
performs the same patching, was not modified to remove its patching logic. Since both
plugins are registered unconditionally, the `handler-legacy.server.ts` plugin will run
first (due to alphabetical sorting), followed by `sentry.server.ts`, resulting in the
event handler being patched twice. This double-wrapping could lead to redundant
operations and potential unexpected side effects or performance degradation.

Did we get this right? 👍 / 👎 to inform future reviews.

"devDependencies": {
"@nuxt/module-builder": "^0.8.4",
"@nuxt/nitro-server": "^3.21.1",
"nitro": "^3.0.260311-beta",
Copy link
Member Author

Choose a reason for hiding this comment

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

I installed the beta version as the 3.0.0 version is using different types 😬

@s1gr1d s1gr1d requested review from Lms24 and chargome March 20, 2026 13:06
Copy link

@cursor cursor bot left a comment

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

Bugbot Autofix prepared a fix for the issue found in the latest run.

  • ✅ Fixed: Handler gets patched twice causing double Proxy wrapping
    • Removed the duplicate handler patching logic from sentry.server.ts so only the dedicated handler-legacy.server.ts plugin wraps nitroApp.h3App.handler once.

Create PR

Or push these changes by commenting:

@cursor push b1f2cae2f5
Preview (b1f2cae2f5)
diff --git a/packages/nuxt/src/runtime/plugins/sentry.server.ts b/packages/nuxt/src/runtime/plugins/sentry.server.ts
--- a/packages/nuxt/src/runtime/plugins/sentry.server.ts
+++ b/packages/nuxt/src/runtime/plugins/sentry.server.ts
@@ -1,11 +1,5 @@
-import {
-  debug,
-  flushIfServerless,
-  getDefaultIsolationScope,
-  getIsolationScope,
-  withIsolationScope,
-} from '@sentry/core';
-import type { EventHandler, H3Event } from 'h3';
+import { debug } from '@sentry/core';
+import type { H3Event } from 'h3';
 import type { NitroAppPlugin } from 'nitropack';
 import type { NuxtRenderHTMLContext } from 'nuxt/app';
 import { sentryCaptureErrorHook } from '../hooks/captureErrorHook';
@@ -13,8 +7,6 @@
 import { addSentryTracingMetaTags } from '../utils';
 
 export default (nitroApp => {
-  nitroApp.h3App.handler = patchEventHandler(nitroApp.h3App.handler);
-
   nitroApp.hooks.hook('beforeResponse', updateRouteBeforeResponse);
 
   nitroApp.hooks.hook('error', sentryCaptureErrorHook);
@@ -37,26 +29,3 @@
     }
   });
 }) satisfies NitroAppPlugin;
-
-function patchEventHandler(handler: EventHandler): EventHandler {
-  return new Proxy(handler, {
-    async apply(handlerTarget, handlerThisArg, handlerArgs: Parameters<EventHandler>) {
-      const isolationScope = getIsolationScope();
-      const newIsolationScope = isolationScope === getDefaultIsolationScope() ? isolationScope.clone() : isolationScope;
-
-      debug.log(
-        `Patched h3 event handler. ${
-          isolationScope === newIsolationScope ? 'Using existing' : 'Created new'
-        } isolation scope.`,
-      );
-
-      return withIsolationScope(newIsolationScope, async () => {
-        try {
-          return await handlerTarget.apply(handlerThisArg, handlerArgs);
-        } finally {
-          await flushIfServerless();
-        }
-      });
-    },
-  });
-}

This Bugbot Autofix run was free. To enable autofix for future PRs, go to the Cursor dashboard.

@github-actions
Copy link
Contributor

github-actions bot commented Mar 20, 2026

size-limit report 📦

⚠️ Warning: Base artifact is not the latest one, because the latest workflow run is not done yet. This may lead to incorrect results. Try to re-run all tests to get up to date results.

Path Size % Change Change
@sentry/browser 25.69 kB +0.2% +49 B 🔺
@sentry/browser - with treeshaking flags 24.17 kB +0.14% +33 B 🔺
@sentry/browser (incl. Tracing) 42.67 kB +0.13% +54 B 🔺
@sentry/browser (incl. Tracing, Profiling) 47.33 kB +0.12% +55 B 🔺
@sentry/browser (incl. Tracing, Replay) 81.48 kB +0.08% +57 B 🔺
@sentry/browser (incl. Tracing, Replay) - with treeshaking flags 71.06 kB +0.1% +69 B 🔺
@sentry/browser (incl. Tracing, Replay with Canvas) 86.17 kB +0.06% +50 B 🔺
@sentry/browser (incl. Tracing, Replay, Feedback) 98.41 kB +0.04% +36 B 🔺
@sentry/browser (incl. Feedback) 42.48 kB +0.08% +30 B 🔺
@sentry/browser (incl. sendFeedback) 30.35 kB +0.15% +43 B 🔺
@sentry/browser (incl. FeedbackAsync) 35.4 kB +0.12% +39 B 🔺
@sentry/browser (incl. Metrics) 26.96 kB +0.15% +38 B 🔺
@sentry/browser (incl. Logs) 27.1 kB +0.12% +32 B 🔺
@sentry/browser (incl. Metrics & Logs) 27.78 kB +0.15% +39 B 🔺
@sentry/react 27.45 kB +0.22% +58 B 🔺
@sentry/react (incl. Tracing) 45.01 kB +0.14% +60 B 🔺
@sentry/vue 30.13 kB +0.16% +46 B 🔺
@sentry/vue (incl. Tracing) 44.52 kB +0.09% +39 B 🔺
@sentry/svelte 25.7 kB +0.16% +40 B 🔺
CDN Bundle 28.35 kB +0.27% +75 B 🔺
CDN Bundle (incl. Tracing) 43.57 kB +0.15% +62 B 🔺
CDN Bundle (incl. Logs, Metrics) 29.22 kB +0.27% +77 B 🔺
CDN Bundle (incl. Tracing, Logs, Metrics) 44.43 kB +0.17% +75 B 🔺
CDN Bundle (incl. Replay, Logs, Metrics) 68.29 kB +0.13% +85 B 🔺
CDN Bundle (incl. Tracing, Replay) 80.41 kB +0.1% +73 B 🔺
CDN Bundle (incl. Tracing, Replay, Logs, Metrics) 81.31 kB +0.1% +76 B 🔺
CDN Bundle (incl. Tracing, Replay, Feedback) 85.97 kB +0.12% +103 B 🔺
CDN Bundle (incl. Tracing, Replay, Feedback, Logs, Metrics) 86.86 kB +0.1% +86 B 🔺
CDN Bundle - uncompressed 82.7 kB +0.1% +77 B 🔺
CDN Bundle (incl. Tracing) - uncompressed 128.62 kB +0.05% +64 B 🔺
CDN Bundle (incl. Logs, Metrics) - uncompressed 85.57 kB +0.1% +77 B 🔺
CDN Bundle (incl. Tracing, Logs, Metrics) - uncompressed 131.49 kB +0.05% +64 B 🔺
CDN Bundle (incl. Replay, Logs, Metrics) - uncompressed 209.22 kB +0.05% +102 B 🔺
CDN Bundle (incl. Tracing, Replay) - uncompressed 245.5 kB +0.04% +89 B 🔺
CDN Bundle (incl. Tracing, Replay, Logs, Metrics) - uncompressed 248.35 kB +0.04% +89 B 🔺
CDN Bundle (incl. Tracing, Replay, Feedback) - uncompressed 258.41 kB +0.04% +89 B 🔺
CDN Bundle (incl. Tracing, Replay, Feedback, Logs, Metrics) - uncompressed 261.26 kB +0.04% +89 B 🔺
@sentry/nextjs (client) 47.4 kB +0.08% +37 B 🔺
@sentry/sveltekit (client) 43.12 kB +0.12% +51 B 🔺
@sentry/node-core 56.42 kB +0.13% +73 B 🔺
@sentry/node 173.38 kB +0.13% +221 B 🔺
@sentry/node - without tracing 96.43 kB +0.1% +87 B 🔺
@sentry/aws-serverless 113.44 kB +0.1% +103 B 🔺

View base workflow run

@github-actions
Copy link
Contributor

github-actions bot commented Mar 20, 2026

node-overhead report 🧳

Note: This is a synthetic benchmark with a minimal express app and does not necessarily reflect the real-world performance impact in an application.
⚠️ Warning: Base artifact is not the latest one, because the latest workflow run is not done yet. This may lead to incorrect results. Try to re-run all tests to get up to date results.

Scenario Requests/s % of Baseline Prev. Requests/s Change %
GET Baseline 8,960 - 9,137 -2%
GET With Sentry 1,708 19% 1,642 +4%
GET With Sentry (error only) 6,130 68% 6,078 +1%
POST Baseline 1,194 - 1,202 -1%
POST With Sentry 601 50% 585 +3%
POST With Sentry (error only) 1,050 88% 1,058 -1%
MYSQL Baseline 3,264 - 3,231 +1%
MYSQL With Sentry 496 15% 484 +2%
MYSQL With Sentry (error only) 2,645 81% 2,645 -

View base workflow run

Copy link
Member

@logaretm logaretm left a comment

Choose a reason for hiding this comment

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

LGTM, if we need to check Nitro's version we can probably do something like this and read it from the package json.

is there a possibility that Nuxt 4 uses Nitro v3?

@s1gr1d
Copy link
Member Author

s1gr1d commented Mar 23, 2026

is there a possibility that Nuxt 4 uses Nitro v3?

Nuxt 4 can enable a compatibility mode for v5, but there's still Nuxt v4 and Nitro v2 packages installed. It's just using the new APIs.

The idea with reading it from the package json sounds good - I could add this (along with an E2E test) in another PR.

@s1gr1d s1gr1d enabled auto-merge (squash) March 23, 2026 09:55
@s1gr1d s1gr1d merged commit 3fafdb4 into develop Mar 23, 2026
237 checks passed
@s1gr1d s1gr1d deleted the sig/nuxt-v5-handler branch March 23, 2026 10:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Nuxt] Extract handler patching as extra plugin for Nuxt v5 (different nitroApp structure)

3 participants