Skip to content

feat(deps)!: upgrade to nitro v3#33005

Merged
danielroe merged 247 commits intomainfrom
feat/nitro-v3
Mar 12, 2026
Merged

feat(deps)!: upgrade to nitro v3#33005
danielroe merged 247 commits intomainfrom
feat/nitro-v3

Conversation

@danielroe
Copy link
Copy Markdown
Member

@danielroe danielroe commented Aug 19, 2025

🔗 Linked issue

📚 Description

this is a WIP PR to update to nitro v3

🚧 TODO

  • migrate to rou3 and implement compiled router (requires build manifest -> JS chunk)
  • import.meta.prerender is set in tests/importmaps 🤔
  • type for $fetch from nuxt/test-utils
  • consider removing use of destr in Nuxt (to align with h3)
  • feat(deps): upgrade hookable to v6 #33905
  • decorators: transformer: ecma decorators oxc-project/oxc#9170
  • client-side errors with fatal: true
  • cookies are duplicated in response headers
  • custom base url doesn't seem to apply to public assets dirs
  • dev server error
  • move $fetch to auto-import
  • add upgrade guide + migration documentation
  • nuxt/devtools is not yet compatible (dependent on nitro.storage)
  • drop HTTPError from Nuxt's createError
Details
ERROR  [unhandledRejection] Parse Error: Data after Connection: close
  at Socket.socketOnData (node:_http_client:614:22)
  at Socket.emit (node:events:507:28)
  at addChunk (node:internal/streams/readable:559:12)
  at readableAddChunkPushByteMode (node:internal/streams/readable:510:3)
  at Readable.push (node:internal/streams/readable:390:5)
  at Pipe.onStreamRead (node:internal/stream_base_commons:189:23)

🐛 upstream bugs in nitro

  • overriding ignore patterns per asset dir no longer works - so public assets starting with - are not included in the build

🪲 other upstream bugs

  • jsdom => TypeError: Failed to execute 'any' on 'AbortSignal': parameter 1's element is not of type 'AbortSignal'.
  • srvx/happy-dom => Headers.append: "() => { throw new Error("Headers are frozen"); }" is an invalid header value.

⚗️ upstream features in nitro

that have been dropped or are not yet implemented

Co-authored-by: Pooya Parsa <pooya@pi0.io>
@bolt-new-by-stackblitz
Copy link
Copy Markdown

Review PR in StackBlitz Codeflow Run & review this pull request in StackBlitz Codeflow.

@pkg-pr-new
Copy link
Copy Markdown

pkg-pr-new bot commented Aug 19, 2025

Open in StackBlitz

@nuxt/kit

npm i https://pkg.pr.new/@nuxt/kit@33005

@nuxt/nitro-server

npm i https://pkg.pr.new/@nuxt/nitro-server@33005

nuxt

npm i https://pkg.pr.new/nuxt@33005

@nuxt/rspack-builder

npm i https://pkg.pr.new/@nuxt/rspack-builder@33005

@nuxt/schema

npm i https://pkg.pr.new/@nuxt/schema@33005

@nuxt/vite-builder

npm i https://pkg.pr.new/@nuxt/vite-builder@33005

@nuxt/webpack-builder

npm i https://pkg.pr.new/@nuxt/webpack-builder@33005

commit: 29ed7fb

@codspeed-hq
Copy link
Copy Markdown

codspeed-hq bot commented Aug 19, 2025

Merging this PR will degrade performance by 36.75%

⚠️ Different runtime environments detected

Some benchmarks with significant performance changes were compared across different runtime environments,
which may affect the accuracy of the results.

Open the report in CodSpeed to investigate

⚡ 1 improved benchmark
❌ 2 regressed benchmarks
✅ 17 untouched benchmarks
⏩ 3 skipped benchmarks1

⚠️ Please fix the performance issues or acknowledge them on CodSpeed.

Performance Changes

Benchmark BASE HEAD Efficiency
writeTypes in the basic-types fixture 84.6 ms 37.3 ms ×2.3
loadNuxt in the basic test fixture 397.6 ms 500.1 ms -20.51%
loadNuxt in an empty directory 173.9 ms 274.9 ms -36.75%

Comparing feat/nitro-v3 (cbced9e) with main (d5d8ce2)

Open in CodSpeed

Footnotes

  1. 3 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 8

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
docs/2.directory-structure/1.server.md (1)

325-352: ⚠️ Potential issue | 🟡 Minor

The note below is now misleading.

The example correctly switched to useRuntimeConfig() with no argument, but the note still says passing event is recommended to pick up env-var overrides. The current implementation ignores that parameter, so this guidance should be removed or rewritten before it sends readers in the wrong direction.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/2.directory-structure/1.server.md` around lines 325 - 352, The note is
misleading about passing the event to useRuntimeConfig; update the docs around
the example (the defineEventHandler block and the useRuntimeConfig call) to
remove or rewrite the statement that "Giving the `event` as argument to
`useRuntimeConfig` is recommended" — instead state that useRuntimeConfig should
be called without arguments (useRuntimeConfig()) because the implementation no
longer accepts/uses an event parameter and environment-variable overrides are
applied automatically at runtime; reference the example's defineEventHandler and
useRuntimeConfig symbols so the change is made next to that snippet.
♻️ Duplicate comments (2)
packages/nitro-server/src/runtime/utils/cache-driver.js (1)

34-35: ⚠️ Potential issue | 🟠 Major

Handle falsy cached values without ||.

This still treats valid cached values like 0, '' and false as misses, so those entries will bypass the LRU and hit disk on every read. Check explicitly for undefined instead.

Suggested change
    async getItem (key, opts) {
-      return await lru.getItem(key, opts) || await fs.getItem(normalizeFsKey(key), opts)
+      const cached = await lru.getItem(key, opts)
+      if (cached !== undefined) {
+        return cached
+      }
+      return await fs.getItem(normalizeFsKey(key), opts)
    },
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/nitro-server/src/runtime/utils/cache-driver.js` around lines 34 -
35, The getItem implementation in function getItem currently treats falsy cached
values as misses by using "||" between lru.getItem and fs.getItem; change the
logic to call lru.getItem(key, opts) and check explicitly for undefined (not
falsy) before falling back to fs.getItem(normalizeFsKey(key), opts) so values
like 0, '', and false are returned from the LRU without hitting disk; update
references in getItem to use the explicit undefined check with lru and fs and
preserve existing normalizeFsKey usage.
packages/nitro-server/src/runtime/utils/error.ts (1)

13-20: ⚠️ Potential issue | 🟠 Major

Use the normalised request path for /api/ detection.

new FastURL(event.req.url).pathname still reflects the raw incoming URL. With a non-root app.baseURL, requests like /foo/api/* no longer satisfy the /api/ heuristic here, so API errors can negotiate as HTML instead of JSON. Please run these checks against the normalised path, or strip the base URL before evaluating the pathname.

#!/bin/bash
set -euo pipefail

printf '== Current error helper ==\n'
sed -n '1,80p' packages/nitro-server/src/runtime/utils/error.ts

printf '\n== Base URL normalisation middleware ==\n'
sed -n '1,220p' packages/nitro-server/src/runtime/middleware/base-url.ts

printf '\n== Base URL-related references ==\n'
rg -n -C3 'NUXT_APP_BASE_URL|/foo/api|/foo/url|baseURL'

Expected result: if the middleware strips a prefix or the tests assert paths without /foo, this helper should not key the /api/ check off event.req.url.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/nitro-server/src/runtime/utils/error.ts` around lines 13 - 20, The
code uses new FastURL(event.req.url).pathname (stored in pathname) which
reflects the raw URL and fails when a non-root app.baseURL is in effect; change
the /api/ detection to use the normalised request path produced by the base-url
middleware (or explicitly strip the app baseURL from event.req.url) before
evaluating pathname so pathname.startsWith('/api/') and
pathname.endsWith('.json') run against the normalized path; update the logic in
error helper (where FastURL and hasReqHeader are used) to derive pathname from
the normalised path/context provided by the middleware rather than the raw
event.req.url.
🧹 Nitpick comments (3)
test/basic.test.ts (1)

2308-2308: Resolve or replace this TODO before merge.

A bare TODO here will age badly. If the breaking change is already documented, drop it; otherwise link the exact upgrade note or follow-up issue so the comment stays actionable.

As per coding guidelines "Add comments only to explain complex logic or non-obvious implementations".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/basic.test.ts` at line 2308, Replace the bare TODO comment "// TODO:
document as breaking change" in test/basic.test.ts with an actionable item:
either remove the TODO if the breaking change is already documented, or replace
it with a one-line link to the exact upgrade note or a created follow-up issue
(include the issue number/URL) and a short rationale; ensure the comment
references the exact change it concerns so future readers can find the upgrade
guidance.
test/bundle.test.ts (2)

72-74: Extract the _libs package-name normaliser.

The same _libs/ stripping and .mjs trimming logic is repeated in all three server-bundle tests. Pulling that into a tiny helper would keep future normalisation changes in one place and reduce snapshot drift between cases. As per coding guidelines, keep functions focused and manageable.

Also applies to: 107-109, 140-142

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/bundle.test.ts` around lines 72 - 74, Extract the repeated normalisation
into a small helper (e.g. normalizePackageName) inside the test file and replace
the inline map logic in the three places (the packages = modules.files map at
lines around the three occurrences) to call that helper; the helper should take
a filename string, strip the leading "_libs/" and remove a trailing ".mjs"
(using the same replace calls currently used) and return the normalized package
name so all three server-bundle test cases share the same normalization logic.

66-70: Consider making the _libs exclusion pattern explicit for clarity.

Lines 66, 101 and 134 use !_libs to exclude the dependency payload from serverStats. Although tinyglobby 0.2.x (with default settings) automatically expands this to exclude descendants, using an explicit pattern such as !_libs/** would make the intent clearer and reduce potential maintenance burden if the glob logic is ever revisited.

Also applies to: 101–105, 134–138

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/bundle.test.ts` around lines 66 - 70, Replace the implicit exclusion
pattern '!_libs' with an explicit descendant exclusion '!_libs/**' in the
analyzeSizes calls (e.g., where analyzeSizes([...], serverDir) is invoked to
compute serverStats and modules); update all occurrences referenced in this test
(the calls around serverStats and modules) so the glob intentionally excludes
the _libs directory and its children by using '!_libs/**' to make the intent
clear and robust against glob library changes.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@docs/1.getting-started/18.upgrade.md`:
- Around line 347-349: The note about getRouteRules() is ambiguous; update the
documentation to explicitly reference the Nitro helper import (e.g., from
"nitro/app") and clarify the new server signature and return shape: state that
getRouteRules(method, pathname) is the Nitro server helper (not the Nuxt
composable) and that it returns an object containing { routeRules } instead of
the older getRouteRules(event) form; ensure the text mentions the import source
"nitro/app" and contrasts it with the Nuxt composable that accepts an event.

In `@docs/2.directory-structure/1.server.md`:
- Line 22: Replace the redundant phrasing in the sentence "The handler can
directly return JSON data, a `Promise`, or return a `Response` object." with the
tightened wording "The handler can directly return JSON data, a `Promise`, or a
`Response` object." by removing the duplicated verb; update the line in
docs/2.directory-structure/1.server.md where the handler return types are listed
(the sentence starting "The handler can directly return...") to the new
phrasing.
- Line 25: The page mixes imports from 'nitro/h3' and 'h3' (e.g., the import
using defineEventHandler), causing conflicting guidance; pick the intended
canonical import path (likely 'nitro/h3') and update all snippets that import
from 'h3' to import from the chosen path so every example uses the same module
name and the same defineEventHandler import form across the document.

In `@packages/nitro-server/src/runtime/handlers/error.ts`:
- Around line 103-107: The merge at the end re-injects raw res.headers and
event.res.headers (via setCookies + mergeHeaders) which can restore stale
content-type or content-security-policy; update the merge to first filter those
headers out (e.g. implement a small filterErrorPageHeaders that copies all
header entries except 'content-type' and 'content-security-policy') and pass the
filtered Headers into mergeHeaders instead of using res.headers or (event as
H3Event).res.headers directly; ensure setCookies handling remains unchanged and
reference the existing setCookies, mergeHeaders, headers, res.headers and
event/H3Event symbols when making the change.
- Around line 39-42: The code assigns errorObject.data using the `||=` operator
which will overwrite valid falsy values; update the assignment in the error
handler so that errorObject.data uses the nullish assignment operator (`??=`)
instead of `||=` to only replace when `errorObject.data` is null or undefined
(locate the block that creates `const errorObject = (defaultRes.body || {})
...`, the line `errorObject.data ||= error.data`, and change it to use `??=`),
leaving the subsequent `errorObject.url = event.req.url` unchanged.

In `@packages/nitro-server/src/runtime/handlers/island.ts`:
- Around line 24-25: Replace the two-step cache check (import.meta.prerender &&
await islandCache!.hasItem(islandPath) followed by
islandCache!.getItem(islandPath)) with a single await
islandCache!.getItem(islandPath) call and null/undefined check so we avoid the
TOCTOU and double I/O; when returning the cached value, use the actual stored
type (NuxtIslandResponse) instead of casting to Partial<RenderResponse> and
propagate that correct type back to the caller (update the same pattern found
around the other occurrences that use hasItem/getItem at the referenced spots),
ensuring you reference islandCache, islandPath, getItem, hasItem,
NuxtIslandResponse and RenderResponse while making the type consistent.
- Around line 136-141: The returned island context currently sets props to
destr(context?.props) || {}, which can allow non-object truthy values (e.g.,
true, [], "x") through; change this to normalize/clamp props to a plain object:
call destr(context?.props) into a temporary (e.g., rawProps), then if rawProps
is a non-null object and not an Array use it, otherwise use {}; update the
returned props field to that sanitized object. Reference variables/functions:
destr, context?.props, and the returned props field in island.ts.

In `@test/basic.test.ts`:
- Around line 1214-1218: The test currently asserts the new error shape
(message, status, statusText) but doesn't ensure deprecated aliases are removed;
update the assertions in the blocks around the existing
expect(error).not.toHaveProperty('url') checks to also assert the deprecated
fields are absent by adding negative assertions for 'statusCode' and
'statusMessage' (and mirror the same additions in the other affected block
around lines 1236-1242) so the test fails if old aliases are still serialized.

---

Outside diff comments:
In `@docs/2.directory-structure/1.server.md`:
- Around line 325-352: The note is misleading about passing the event to
useRuntimeConfig; update the docs around the example (the defineEventHandler
block and the useRuntimeConfig call) to remove or rewrite the statement that
"Giving the `event` as argument to `useRuntimeConfig` is recommended" — instead
state that useRuntimeConfig should be called without arguments
(useRuntimeConfig()) because the implementation no longer accepts/uses an event
parameter and environment-variable overrides are applied automatically at
runtime; reference the example's defineEventHandler and useRuntimeConfig symbols
so the change is made next to that snippet.

---

Duplicate comments:
In `@packages/nitro-server/src/runtime/utils/cache-driver.js`:
- Around line 34-35: The getItem implementation in function getItem currently
treats falsy cached values as misses by using "||" between lru.getItem and
fs.getItem; change the logic to call lru.getItem(key, opts) and check explicitly
for undefined (not falsy) before falling back to fs.getItem(normalizeFsKey(key),
opts) so values like 0, '', and false are returned from the LRU without hitting
disk; update references in getItem to use the explicit undefined check with lru
and fs and preserve existing normalizeFsKey usage.

In `@packages/nitro-server/src/runtime/utils/error.ts`:
- Around line 13-20: The code uses new FastURL(event.req.url).pathname (stored
in pathname) which reflects the raw URL and fails when a non-root app.baseURL is
in effect; change the /api/ detection to use the normalised request path
produced by the base-url middleware (or explicitly strip the app baseURL from
event.req.url) before evaluating pathname so pathname.startsWith('/api/') and
pathname.endsWith('.json') run against the normalized path; update the logic in
error helper (where FastURL and hasReqHeader are used) to derive pathname from
the normalised path/context provided by the middleware rather than the raw
event.req.url.

---

Nitpick comments:
In `@test/basic.test.ts`:
- Line 2308: Replace the bare TODO comment "// TODO: document as breaking
change" in test/basic.test.ts with an actionable item: either remove the TODO if
the breaking change is already documented, or replace it with a one-line link to
the exact upgrade note or a created follow-up issue (include the issue
number/URL) and a short rationale; ensure the comment references the exact
change it concerns so future readers can find the upgrade guidance.

In `@test/bundle.test.ts`:
- Around line 72-74: Extract the repeated normalisation into a small helper
(e.g. normalizePackageName) inside the test file and replace the inline map
logic in the three places (the packages = modules.files map at lines around the
three occurrences) to call that helper; the helper should take a filename
string, strip the leading "_libs/" and remove a trailing ".mjs" (using the same
replace calls currently used) and return the normalized package name so all
three server-bundle test cases share the same normalization logic.
- Around line 66-70: Replace the implicit exclusion pattern '!_libs' with an
explicit descendant exclusion '!_libs/**' in the analyzeSizes calls (e.g., where
analyzeSizes([...], serverDir) is invoked to compute serverStats and modules);
update all occurrences referenced in this test (the calls around serverStats and
modules) so the glob intentionally excludes the _libs directory and its children
by using '!_libs/**' to make the intent clear and robust against glob library
changes.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 90dadcee-b74d-4cb5-bc05-a2ff577d00c8

📥 Commits

Reviewing files that changed from the base of the PR and between ef352ea and 9d138ee.

📒 Files selected for processing (15)
  • docs/1.getting-started/18.upgrade.md
  • docs/2.directory-structure/1.server.md
  • packages/nitro-server/src/imports.ts
  • packages/nitro-server/src/runtime/handlers/error.ts
  • packages/nitro-server/src/runtime/handlers/island.ts
  • packages/nitro-server/src/runtime/handlers/renderer.ts
  • packages/nitro-server/src/runtime/middleware/base-url.ts
  • packages/nitro-server/src/runtime/utils/cache-driver.js
  • packages/nitro-server/src/runtime/utils/error.ts
  • packages/nitro-server/src/runtime/utils/renderer/payload.ts
  • packages/nuxt/src/app/composables/cookie.ts
  • packages/nuxt/src/app/composables/ssr.ts
  • packages/nuxt/test/nitro/render-index.ts
  • test/basic.test.ts
  • test/bundle.test.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/nitro-server/src/runtime/utils/renderer/payload.ts

The handler can directly return JSON data, a `Promise`, or return a `Response` object.

```ts twoslash [server/api/hello.ts]
import { defineEventHandler } from 'nitro/h3'
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Avoid mixed h3/nitro/h3 guidance on the same page.

This example now uses nitro/h3, but later snippets on Line 422 and Line 439 still import from h3. If nitro/h3 is the Nitro v3 path we want readers to follow, those examples should be updated too; otherwise this page gives conflicting copy-paste guidance.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/2.directory-structure/1.server.md` at line 25, The page mixes imports
from 'nitro/h3' and 'h3' (e.g., the import using defineEventHandler), causing
conflicting guidance; pick the intended canonical import path (likely
'nitro/h3') and update all snippets that import from 'h3' to import from the
chosen path so every example uses the same module name and the same
defineEventHandler import form across the document.

Comment on lines +24 to +25
if (import.meta.prerender && await islandCache!.hasItem(islandPath)) {
return islandCache!.getItem(islandPath) as Promise<Partial<RenderResponse>>
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Collapse prerender cache lookups to a single read, and fix the cached response type.

hasItem() + getItem() adds an avoidable TOCTOU window and doubles storage I/O. In the response path, Line 25 also casts the cached payload to Partial<RenderResponse> even though Line 103 writes a NuxtIslandResponse, so the cache contract is now misleading.

💡 Suggested change
-  if (import.meta.prerender && await islandCache!.hasItem(islandPath)) {
-    return islandCache!.getItem(islandPath) as Promise<Partial<RenderResponse>>
-  }
+  if (import.meta.prerender) {
+    const cachedIslandResponse = await islandCache!.getItem(islandPath) as NuxtIslandResponse | null
+    if (cachedIslandResponse) {
+      return cachedIslandResponse
+    }
+  }
@@
-  if (import.meta.prerender && await islandPropCache!.hasItem(islandPath)) {
-    // rehydrate props from cache so we can rerender island if cache does not have it any more
-    url = await islandPropCache!.getItem(islandPath) as string
-  }
+  if (import.meta.prerender) {
+    // rehydrate props from cache so we can rerender island if cache does not have it any more
+    const cachedRequestUrl = await islandPropCache!.getItem(islandPath) as string | null
+    if (cachedRequestUrl) {
+      url = cachedRequestUrl
+    }
+  }

Also applies to: 103-104, 117-119

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/nitro-server/src/runtime/handlers/island.ts` around lines 24 - 25,
Replace the two-step cache check (import.meta.prerender && await
islandCache!.hasItem(islandPath) followed by islandCache!.getItem(islandPath))
with a single await islandCache!.getItem(islandPath) call and null/undefined
check so we avoid the TOCTOU and double I/O; when returning the cached value,
use the actual stored type (NuxtIslandResponse) instead of casting to
Partial<RenderResponse> and propagate that correct type back to the caller
(update the same pattern found around the other occurrences that use
hasItem/getItem at the referenced spots), ensuring you reference islandCache,
islandPath, getItem, hasItem, NuxtIslandResponse and RenderResponse while making
the type consistent.

Comment on lines 136 to +141
// Only extract known context fields to prevent arbitrary data injection
return {
url: typeof context?.url === 'string' ? context.url : '/',
id: hashId,
name: componentName,
props: destr(context.props) || {},
props: destr(context?.props) || {},
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Normalise props to a plain object before returning the island context.

Line 141 accepts any truthy destr() result, so inputs like props=true, props=[], or props='"x"' can still flow through as non-object values. This endpoint is already validating request shape, so it is safer to clamp props to an object here as well.

💡 Suggested change
+  const props = destr(context?.props)
   return {
     url: typeof context?.url === 'string' ? context.url : '/',
     id: hashId,
     name: componentName,
-    props: destr(context?.props) || {},
+    props: props && typeof props === 'object' && !Array.isArray(props) ? props : {},
     slots: {},
     components: {},
   }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// Only extract known context fields to prevent arbitrary data injection
return {
url: typeof context?.url === 'string' ? context.url : '/',
id: hashId,
name: componentName,
props: destr(context.props) || {},
props: destr(context?.props) || {},
// Only extract known context fields to prevent arbitrary data injection
const props = destr(context?.props)
return {
url: typeof context?.url === 'string' ? context.url : '/',
id: hashId,
name: componentName,
props: props && typeof props === 'object' && !Array.isArray(props) ? props : {},
slots: {},
components: {},
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/nitro-server/src/runtime/handlers/island.ts` around lines 136 - 141,
The returned island context currently sets props to destr(context?.props) || {},
which can allow non-object truthy values (e.g., true, [], "x") through; change
this to normalize/clamp props to a plain object: call destr(context?.props) into
a temporary (e.g., rawProps), then if rawProps is a non-null object and not an
Array use it, otherwise use {}; update the returned props field to that
sanitized object. Reference variables/functions: destr, context?.props, and the
returned props field in island.ts.

Comment on lines +1214 to +1218
message: 'This is a custom error',
status: 422,
statusText: 'This is a custom error',
})
expect(error).not.toHaveProperty('url')
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Assert that the deprecated error aliases are absent too.

These checks prove the new shape exists, but they still pass if statusCode/statusMessage are serialised alongside status/statusText. That leaves the Nitro v3 error-shape regression only partially covered.

💡 Suggested assertion hardening
     expect(error).toMatchObject({
       message: 'This is a custom error',
       status: 422,
       statusText: 'This is a custom error',
     })
+    expect(error).not.toHaveProperty('statusCode')
+    expect(error).not.toHaveProperty('statusMessage')
     expect(error).not.toHaveProperty('url')
     expect(error).toMatchObject({
       error: true,
       message: 'Page Not Found: /__nuxt_error',
       status: 404,
       statusText: 'Page Not Found: /__nuxt_error',
     })
+    expect(error).not.toHaveProperty('statusCode')
+    expect(error).not.toHaveProperty('statusMessage')
     expect(error).not.toHaveProperty('url')

Also applies to: 1236-1242

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/basic.test.ts` around lines 1214 - 1218, The test currently asserts the
new error shape (message, status, statusText) but doesn't ensure deprecated
aliases are removed; update the assertions in the blocks around the existing
expect(error).not.toHaveProperty('url') checks to also assert the deprecated
fields are absent by adding negative assertions for 'statusCode' and
'statusMessage' (and mirror the same additions in the other affected block
around lines 1236-1242) so the test fails if old aliases are still serialized.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

♻️ Duplicate comments (1)
packages/nuxt/test/nitro/render-index.ts (1)

8-16: ⚠️ Potential issue | 🟠 Major

Handle the top-level rejection path explicitly.

This still leaves failed fetches and the new !res.ok throw to bubble out as an unhandled rejection. Please add a final .catch(...) and include the response body on the error path so broken renders fail clearly.

Suggested fix
 async function renderIndex () {
   const res = await nitroApp.fetch(new Request('/'))
   if (!res.ok) {
-    throw new Error(`Failed to render /: ${res.status} ${res.statusText}`)
+    const body = await res.text()
+    throw new Error(`Failed to render /: ${res.status} ${res.statusText}\n${body}`)
   }
   // eslint-disable-next-line no-console
   console.log(await res.text())
 }
 
-renderIndex()
+renderIndex().catch((error) => {
+  console.error(error)
+  process.exitCode = 1
+})
As per coding guidelines: "Use error handling patterns consistently".
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/nuxt/test/nitro/render-index.ts` around lines 8 - 16, The top-level
promise from renderIndex() can produce unhandled rejections when
nitroApp.fetch(...) fails or when the `if (!res.ok)` branch throws; append a
final `.catch(...)` to the call to renderIndex() to handle all rejections, read
the response body via `res.text()` (or the caught error's response if available)
and include that body text in the thrown/logged error so failed renders surface
clear diagnostics; modify the call site where `renderIndex()` is invoked to
attach `.catch(err => { /* include await res.text() when present and rethrow or
console.error with body */ })` and ensure any asynchronous reading of
`res.text()` is awaited inside the catch before reporting.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@packages/nuxt/test/nitro/render-index.ts`:
- Around line 8-16: The top-level promise from renderIndex() can produce
unhandled rejections when nitroApp.fetch(...) fails or when the `if (!res.ok)`
branch throws; append a final `.catch(...)` to the call to renderIndex() to
handle all rejections, read the response body via `res.text()` (or the caught
error's response if available) and include that body text in the thrown/logged
error so failed renders surface clear diagnostics; modify the call site where
`renderIndex()` is invoked to attach `.catch(err => { /* include await
res.text() when present and rethrow or console.error with body */ })` and ensure
any asynchronous reading of `res.text()` is awaited inside the catch before
reporting.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 37c5b76f-69c4-4f49-b960-3d2466f8dab8

📥 Commits

Reviewing files that changed from the base of the PR and between 9d138ee and 256ac47.

📒 Files selected for processing (1)
  • packages/nuxt/test/nitro/render-index.ts

@danielroe danielroe enabled auto-merge March 11, 2026 23:37
@danielroe danielroe disabled auto-merge March 11, 2026 23:37
@danielroe danielroe enabled auto-merge March 11, 2026 23:38
@danielroe danielroe disabled auto-merge March 11, 2026 23:38
@danielroe danielroe added this pull request to the merge queue Mar 12, 2026
@danielroe danielroe removed this pull request from the merge queue due to a manual request Mar 12, 2026
@danielroe danielroe merged commit 0e08003 into main Mar 12, 2026
46 of 47 checks passed
@danielroe danielroe deleted the feat/nitro-v3 branch March 12, 2026 00:47
This was referenced Mar 12, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants