From d153ea9d237429544a2b94c15d3d4e39ecd3e1ce Mon Sep 17 00:00:00 2001 From: Julien Huang Date: Sat, 1 Mar 2025 08:40:12 +0100 Subject: [PATCH] fix(nuxt): show error page on island page error (#31081) --- .../nuxt/src/app/components/nuxt-island.ts | 23 ++++++++++------ .../components/runtime/server-component.ts | 15 +++++++++-- test/nuxt/island-pages.test.ts | 26 +++++++++++++++++++ 3 files changed, 54 insertions(+), 10 deletions(-) create mode 100644 test/nuxt/island-pages.test.ts diff --git a/packages/nuxt/src/app/components/nuxt-island.ts b/packages/nuxt/src/app/components/nuxt-island.ts index cd3d9cbac5..0eb7526d11 100644 --- a/packages/nuxt/src/app/components/nuxt-island.ts +++ b/packages/nuxt/src/app/components/nuxt-island.ts @@ -184,16 +184,23 @@ export default defineComponent({ ...props.context, props: props.props ? JSON.stringify(props.props) : undefined, })) - const result = import.meta.server || !import.meta.dev ? await r.json() : (r as FetchResponse)._data - // TODO: support passing on more headers - if (import.meta.server && import.meta.prerender) { - const hints = r.headers.get('x-nitro-prerender') - if (hints) { - appendResponseHeader(event!, 'x-nitro-prerender', hints) + try { + const result = import.meta.server || !import.meta.dev ? await r.json() : (r as FetchResponse)._data + // TODO: support passing on more headers + if (import.meta.server && import.meta.prerender) { + const hints = r.headers.get('x-nitro-prerender') + if (hints) { + appendResponseHeader(event!, 'x-nitro-prerender', hints) + } } + setPayload(key, result) + return result + } catch (e: any) { + if (r.status !== 200) { + throw new Error(e.toString(), { cause: r }) + } + throw e } - setPayload(key, result) - return result } async function fetchComponent (force = false) { diff --git a/packages/nuxt/src/components/runtime/server-component.ts b/packages/nuxt/src/components/runtime/server-component.ts index e6bb2b038f..7dc50919ba 100644 --- a/packages/nuxt/src/components/runtime/server-component.ts +++ b/packages/nuxt/src/components/runtime/server-component.ts @@ -2,6 +2,8 @@ import { defineComponent, getCurrentInstance, h, ref } from 'vue' import NuxtIsland from '#app/components/nuxt-island' import { useRoute } from '#app/composables/router' import { isPrerendered } from '#app/composables/payload' +import { createError, showError } from '#app/composables/error' +import { useNuxtApp } from '#app/nuxt' /* @__NO_SIDE_EFFECTS__ */ export const createServerComponent = (name: string) => { @@ -46,10 +48,9 @@ export const createIslandPage = (name: string) => { expose({ refresh: () => islandRef.value?.refresh(), }) - + const nuxtApp = useNuxtApp() const route = useRoute() const path = import.meta.client && await isPrerendered(route.path) ? route.path : route.fullPath.replace(/#.*$/, '') - return () => { return h('div', [ h(NuxtIsland, { @@ -57,6 +58,16 @@ export const createIslandPage = (name: string) => { lazy: props.lazy, ref: islandRef, context: { url: path }, + onError: (e) => { + if (e.cause && e.cause instanceof Response) { + throw createError({ + statusCode: e.cause.status, + statusText: e.cause.statusText, + status: e.cause.status, + }) + } + nuxtApp.runWithContext(() => showError(e)) + }, }, slots), ]) } diff --git a/test/nuxt/island-pages.test.ts b/test/nuxt/island-pages.test.ts new file mode 100644 index 0000000000..ca3c4719a0 --- /dev/null +++ b/test/nuxt/island-pages.test.ts @@ -0,0 +1,26 @@ +import { mountSuspended } from '@nuxt/test-utils/runtime' +import { flushPromises } from '@vue/test-utils' +import { describe, expect, it, vi } from 'vitest' +import { Suspense } from 'vue' +import { createIslandPage } from '~/packages/nuxt/src/components/runtime/server-component' + +vi.mock('#app/composables/error', async (og) => { + return { + ...(await og()), + showError: vi.fn(), + } +}) + +describe('Island pages', () => { + it('expect to show error', async () => { + await mountSuspended({ + setup () { + return () => h(Suspense, {}, { + default: () => h(createIslandPage('pagedontexist')), + }) + }, + }) + await flushPromises() + expect(showError).toHaveBeenCalledOnce() + }) +})