Skip to content

Conversation

@huang-julien
Copy link
Member

🔗 Linked issue

fix #33237

📚 Description

This PR fixes an issue in Nuxt island renderer not correctly handling errors

@bolt-new-by-stackblitz
Copy link

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

@coderabbitai
Copy link

coderabbitai bot commented Sep 23, 2025

Walkthrough

The island handler catch block now uses err, calls app:error with err, rethrows err, and after rendering will throw ssrContext.payload.error if present. NuxtIsland imports createError, selects server fetch or globalThis.fetch, always parses responses with r.json(), and throws createError when r.ok is false. Tests add a server-only page that raises a 404 via showError(createError(...)), a link to it on the index page, and an integration test that navigates client-side to assert the error page appears. Nuxt island tests were refactored to use a createServer helper and include ServerHandler typing; mocked fetch responses were normalised.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (4 passed)
Check name Status Explanation
Title Check ✅ Passed The title succinctly follows conventional commit style and clearly states that the fix concerns handling errors in the island renderer, which aligns with the primary change in the pull request.
Linked Issues Check ✅ Passed The code modifications correctly propagate errors thrown by server-only pages through the island renderer and include a regression test that verifies a 404 error is returned, satisfying the objective of issue #33237.
Out of Scope Changes Check ✅ Passed All modifications pertain to updating error propagation logic in the island handler, adjusting the NuxtIsland component to throw errors on non-OK responses, and adding tests for this behavior, which are all within the scope of the linked issue objectives.
Description Check ✅ Passed The PR description directly references the linked issue and describes fixing error handling in the Nuxt island renderer, which aligns with the changes introduced in the code.
✨ Finishing touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix/servercomponents_error

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 33b96b9 and 26b56e8.

📒 Files selected for processing (1)
  • packages/nuxt/src/app/components/nuxt-island.ts (3 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Follow standard TypeScript conventions and best practices

Files:

  • packages/nuxt/src/app/components/nuxt-island.ts
🧬 Code graph analysis (1)
packages/nuxt/src/app/components/nuxt-island.ts (1)
packages/nuxt/src/app/composables/error.ts (1)
  • createError (64-79)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: build
  • GitHub Check: code
🔇 Additional comments (4)
packages/nuxt/src/app/components/nuxt-island.ts (4)

12-12: LGTM! Proper error utility imported.

The import of createError enables structured error creation with status codes, aligning with the PR's objective to fix island error handling.


208-210: Excellent error handling for non-OK responses.

Checking r.ok and throwing a structured error with createError ensures that HTTP error status codes (404, 500, etc.) are properly propagated. This directly addresses the PR objective to fix island error handling.


212-212: Simplified response parsing is cleaner.

Always using r.json() removes conditional branches and improves code clarity. This is safe because the response format should be consistent across server/client contexts.


96-96: Verify server fetch returns a standard Response-like object
Ensure that event!.fetch truly returns an object compatible with the standard Response API (including .ok, .status, .statusText, and .json()), as its type signature wasn’t found in the codebase—please confirm.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🧪 Early access (Sonnet 4.5): enabled

We are currently testing the Sonnet 4.5 model, which is expected to improve code review quality. However, this model may lead to increased noise levels in the review comments. Please disable the early access features if the noise level causes any inconvenience.

Note:

  • Public repositories are always opted into early access features.
  • You can enable or disable early access features from the CodeRabbit UI or by updating the CodeRabbit configuration file.

Comment @coderabbitai help to get the list of available commands and usage tips.

@pkg-pr-new
Copy link

pkg-pr-new bot commented Sep 23, 2025

Open in StackBlitz

@nuxt/kit

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

nuxt

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

@nuxt/rspack-builder

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

@nuxt/schema

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

@nuxt/vite-builder

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

@nuxt/webpack-builder

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

commit: 26b56e8

@danielroe
Copy link
Member

do you think we could add a regression test?

@codspeed-hq
Copy link

codspeed-hq bot commented Sep 23, 2025

CodSpeed Performance Report

Merging #33302 will not alter performance

Comparing fix/servercomponents_error (26b56e8) with main (849f7fb)

Summary

✅ 10 untouched

@huang-julien huang-julien marked this pull request as draft September 23, 2025 13:30
@huang-julien
Copy link
Member Author

yes, i always forget to open as draft 😅

@huang-julien huang-julien marked this pull request as ready for review September 28, 2025 14:58
Copy link

@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: 0

Caution

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

⚠️ Outside diff range comments (1)
packages/nuxt/src/app/components/nuxt-island.ts (1)

209-231: Prevent ReferenceError when handling non-OK island responses

If the fetch returns a non-OK status we now throw createError from inside the .then before r has been initialised. Control then drops into the catch, where r.status immediately raises ReferenceError: Cannot access 'r' before initialization, turning the intended 404 into a 500 and breaking the regression you're aiming to add. Please ensure the response variable is initialised before the try/catch, or guard the catch before touching r, so that we rethrow the original Nuxt error cleanly.

-      const r = await eventFetch(withQuery(((import.meta.dev && import.meta.client) || props.source) ? url : joinURL(config.app.baseURL ?? '', url), {
-        ...props.context,
-        props: props.props ? JSON.stringify(props.props) : undefined,
-      })).then((response) => {
-        if (!response.ok) {
-          throw createError({ statusCode: response.status, statusMessage: response.statusText })
-        }
-        return response
-      })
-      try {
-        const result = import.meta.server || !import.meta.dev ? await r.json() : (r as FetchResponse<NuxtIslandResponse>)._data
+      let r: FetchResponse<NuxtIslandResponse> | Response | undefined
+      try {
+        r = await eventFetch(withQuery(((import.meta.dev && import.meta.client) || props.source) ? url : joinURL(config.app.baseURL ?? '', url), {
+          ...props.context,
+          props: props.props ? JSON.stringify(props.props) : undefined,
+        })).then((response) => {
+          if (!response.ok) {
+            throw createError({ statusCode: response.status, statusMessage: response.statusText })
+          }
+          return response
+        })
+        const result = import.meta.server || !import.meta.dev ? await r.json() : (r as FetchResponse<NuxtIslandResponse>)._data
         setPayload(key, result)
         return result
-      } catch (e: any) {
-        if (r.status !== 200) {
+      } catch (e: any) {
+        if (!r) {
+          throw e
+        }
+        if (r.status !== 200) {
           throw new Error(e.toString(), { cause: r })
         }
         throw e
       }
🧹 Nitpick comments (1)
test/nuxt/nuxt-island.test.ts (1)

85-122: Ensure test servers close on failure

If one of the assertions throws, the close() call is skipped and the srvx listener can bleed into later tests. Wrapping the mount/expect block in a try/finally (here and in the base-URL variant just below) keeps the suite robust.

-    const { server, port } = await createServer(handler)
-    const wrapper = await mountSuspended(NuxtIsland, {
-      props: {
-        name: 'Test',
-        source: `http://localhost:${port}`,
-      },
-    })
-    expect(wrapper.html()).toMatchInlineSnapshot('"<div>hello world from another server</div>"')
-    await server.close()
+    const { server, port } = await createServer(handler)
+    try {
+      const wrapper = await mountSuspended(NuxtIsland, {
+        props: {
+          name: 'Test',
+          source: `http://localhost:${port}`,
+        },
+      })
+      expect(wrapper.html()).toMatchInlineSnapshot('"<div>hello world from another server</div>"')
+    } finally {
+      await server.close()
+    }
@@
-    const { server, port } = await createServer(handler)
-    const wrapper = await mountSuspended(NuxtIsland, {
-      props: {
-        name: 'Test',
-        source: `http://localhost:${port}/app`,
-      },
-    })
-    expect(wrapper.html()).toMatchInlineSnapshot('"<div>hello world from another server</div>"')
-    expect(url!.startsWith(`http://localhost:${port}/app/__nuxt_island`)).toBe(true)
-    await server.close()
+    const { server, port } = await createServer(handler)
+    try {
+      const wrapper = await mountSuspended(NuxtIsland, {
+        props: {
+          name: 'Test',
+          source: `http://localhost:${port}/app`,
+        },
+      })
+      expect(wrapper.html()).toMatchInlineSnapshot('"<div>hello world from another server</div>"')
+      expect(url!.startsWith(`http://localhost:${port}/app/__nuxt_island`)).toBe(true)
+    } finally {
+      await server.close()
+    }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 024469f and 53c65d5.

📒 Files selected for processing (6)
  • packages/nuxt/src/app/components/nuxt-island.ts (2 hunks)
  • test/basic.test.ts (1 hunks)
  • test/bundle.test.ts (1 hunks)
  • test/fixtures/basic/app/pages/index.vue (1 hunks)
  • test/fixtures/basic/app/pages/server-components/lost-page.server.vue (1 hunks)
  • test/nuxt/nuxt-island.test.ts (12 hunks)
🧰 Additional context used
📓 Path-based instructions (3)
**/*.vue

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Use <script setup lang="ts"> and the composition API when creating Vue components

Files:

  • test/fixtures/basic/app/pages/server-components/lost-page.server.vue
  • test/fixtures/basic/app/pages/index.vue
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Follow standard TypeScript conventions and best practices

Files:

  • test/basic.test.ts
  • test/bundle.test.ts
  • packages/nuxt/src/app/components/nuxt-island.ts
  • test/nuxt/nuxt-island.test.ts
**/*.{test,spec}.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Write unit tests for core functionality using vitest

Files:

  • test/basic.test.ts
  • test/bundle.test.ts
  • test/nuxt/nuxt-island.test.ts
🧠 Learnings (1)
📚 Learning: 2025-07-18T16:46:07.446Z
Learnt from: CR
PR: nuxt/nuxt#0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-07-18T16:46:07.446Z
Learning: Applies to **/*.{test,spec}.{ts,tsx,js,jsx} : Write unit tests for core functionality using `vitest`

Applied to files:

  • test/nuxt/nuxt-island.test.ts
🧬 Code graph analysis (2)
test/basic.test.ts (1)
test/utils.ts (1)
  • renderPage (11-50)
packages/nuxt/src/app/components/nuxt-island.ts (3)
packages/nuxt/src/app/composables/error.ts (1)
  • createError (64-79)
packages/nuxt/src/app/index.ts (1)
  • createError (6-6)
packages/nuxt/src/app/composables/index.ts (1)
  • createError (7-7)
🔇 Additional comments (1)
test/nuxt/nuxt-island.test.ts (1)

10-23: Helper centralises server setup

Consolidating the srvx bootstrapping into createServer keeps these tests much tidier and easier to follow.

await page.locator('#server-page').waitFor()
})

it('should show error on 404 error for server pages during client navigation', async () => {
Copy link
Member

Choose a reason for hiding this comment

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

currently the 'lost page' renders differently on client-side navigation vs hard reload. (the message is different, for example)

ideally we'd make it the same - do you have any ideas for that?

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 just removed the usage of $fetch.raw . We had 503 http issues 2 years ago, seems to be fixed now;

Copy link

@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: 0

🧹 Nitpick comments (1)
packages/nuxt/src/app/components/nuxt-island.ts (1)

210-212: Preserve response context and align error shape with catch path

Throwing createError on non‑OK is correct. To aid debugging and keep a consistent NuxtError on all failure paths, include the response as the error cause (and optionally the URL), and mirror this in the catch branch that currently throws a plain Error.

Apply this diff to enrich the thrown error on non‑OK:

-      if (!r.ok) {
-        throw createError({ statusCode: r.status, statusMessage: r.statusText })
-      }
+      if (!r.ok) {
+        throw createError({
+          statusCode: r.status,
+          statusMessage: r.statusText,
+          cause: r,
+          data: { url: (r as Response).url }
+        })
+      }

And update the catch branch below to use createError as well (outside the changed hunk, shown for clarity):

} catch (e: any) {
  if (r.status !== 200) {
    throw createError({
      statusCode: r.status,
      statusMessage: r.statusText,
      cause: r,
      data: { url: (r as Response).url }
    })
  }
  throw e
}
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 53c65d5 and 87be1f6.

📒 Files selected for processing (2)
  • packages/nuxt/src/app/components/nuxt-island.ts (2 hunks)
  • test/basic.test.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • test/basic.test.ts
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Follow standard TypeScript conventions and best practices

Files:

  • packages/nuxt/src/app/components/nuxt-island.ts
🧬 Code graph analysis (1)
packages/nuxt/src/app/components/nuxt-island.ts (1)
packages/nuxt/src/app/composables/error.ts (1)
  • createError (64-79)
🔇 Additional comments (1)
packages/nuxt/src/app/components/nuxt-island.ts (1)

13-13: Import looks good

Importing createError locally keeps error construction consistent with server/runtime handling.

import type { ActiveHeadEntry, SerializableHead } from '@unhead/vue'
import { randomUUID } from 'uncrypto'
import { joinURL, withQuery } from 'ufo'
import type { FetchResponse } from 'ofetch'
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change

Copy link
Member

@danielroe danielroe left a comment

Choose a reason for hiding this comment

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

great! ❤️

@danielroe danielroe merged commit be9706d into main Sep 30, 2025
83 of 84 checks passed
@danielroe danielroe deleted the fix/servercomponents_error branch September 30, 2025 15:12
@github-actions github-actions bot mentioned this pull request Sep 30, 2025
@github-actions github-actions bot mentioned this pull request Oct 6, 2025
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.

server-only page doesn't return createError(404)

3 participants