From 197de3ecbbb74bad046ad97bd398a27e1c80c700 Mon Sep 17 00:00:00 2001 From: Daniel Roe Date: Wed, 26 Apr 2023 14:36:59 +0100 Subject: [PATCH] fix(nuxt): call `app:error` in SSR before rendering error page (#20511) --- docs/1.getting-started/8.error-handling.md | 1 + packages/nuxt/src/app/composables/cookie.ts | 9 +++++---- packages/nuxt/src/core/runtime/nitro/renderer.ts | 6 ++++-- test/basic.test.ts | 1 + test/bundle.test.ts | 2 +- test/fixtures/basic/pages/error.vue | 1 + 6 files changed, 13 insertions(+), 7 deletions(-) diff --git a/docs/1.getting-started/8.error-handling.md b/docs/1.getting-started/8.error-handling.md index 0a6e7144ee..eba5308944 100644 --- a/docs/1.getting-started/8.error-handling.md +++ b/docs/1.getting-started/8.error-handling.md @@ -41,6 +41,7 @@ This includes: * running Nuxt plugins * processing `app:created` and `app:beforeMount` hooks +* rendering your Vue app to HTML (on the server) * mounting the app (on client-side), though you should handle this case with `onErrorCaptured` or with `vue:error` * processing the `app:mounted` hook diff --git a/packages/nuxt/src/app/composables/cookie.ts b/packages/nuxt/src/app/composables/cookie.ts index e36957a343..353229a197 100644 --- a/packages/nuxt/src/app/composables/cookie.ts +++ b/packages/nuxt/src/app/composables/cookie.ts @@ -48,11 +48,12 @@ export function useCookie (name: string, _opts?: } } const unhook = nuxtApp.hooks.hookOnce('app:rendered', writeFinalCookieValue) - nuxtApp.hooks.hookOnce('app:redirected', () => { - // don't write cookie subsequently when app:rendered is called - unhook() + const writeAndUnhook = () => { + unhook() // don't write cookie subsequently when app:rendered is called return writeFinalCookieValue() - }) + } + nuxtApp.hooks.hookOnce('app:error', writeAndUnhook) + nuxtApp.hooks.hookOnce('app:redirected', writeAndUnhook) } return cookie as CookieRef diff --git a/packages/nuxt/src/core/runtime/nitro/renderer.ts b/packages/nuxt/src/core/runtime/nitro/renderer.ts index 58b8d13ee0..5a48191660 100644 --- a/packages/nuxt/src/core/runtime/nitro/renderer.ts +++ b/packages/nuxt/src/core/runtime/nitro/renderer.ts @@ -244,9 +244,11 @@ export default defineRenderHandler(async (event) => { writeEarlyHints(event, link) } - const _rendered = await renderer.renderToString(ssrContext).catch((error) => { + const _rendered = await renderer.renderToString(ssrContext).catch(async (error) => { // Use explicitly thrown error in preference to subsequent rendering errors - throw (!ssrError && ssrContext.payload?.error) || error + const _err = (!ssrError && ssrContext.payload?.error) || error + await ssrContext.nuxt?.hooks.callHook('app:error', _err) + throw _err }) await ssrContext.nuxt?.hooks.callHook('app:rendered', { ssrContext }) diff --git a/test/basic.test.ts b/test/basic.test.ts index ee52af0989..14e5a57d8a 100644 --- a/test/basic.test.ts +++ b/test/basic.test.ts @@ -571,6 +571,7 @@ describe('errors', () => { it('should render a HTML error page', async () => { const res = await fetch('/error') + expect(res.headers.get('Set-Cookie')).toBe('some-error=was%20set; Path=/') expect(await res.text()).toContain('This is a custom error') }) diff --git a/test/bundle.test.ts b/test/bundle.test.ts index 4ecf36a2bb..da2f1f322f 100644 --- a/test/bundle.test.ts +++ b/test/bundle.test.ts @@ -45,7 +45,7 @@ describe.skipIf(isWindows || process.env.TEST_BUILDER === 'webpack' || process.e it('default server bundle size', async () => { stats.server = await analyzeSizes(['**/*.mjs', '!node_modules'], serverDir) - expect(roundToKilobytes(stats.server.totalBytes)).toMatchInlineSnapshot('"67.2k"') + expect(roundToKilobytes(stats.server.totalBytes)).toMatchInlineSnapshot('"67.3k"') const modules = await analyzeSizes('node_modules/**/*', serverDir) expect(roundToKilobytes(modules.totalBytes)).toMatchInlineSnapshot('"2657k"') diff --git a/test/fixtures/basic/pages/error.vue b/test/fixtures/basic/pages/error.vue index a8fe5801bd..6a97a8e9ce 100644 --- a/test/fixtures/basic/pages/error.vue +++ b/test/fixtures/basic/pages/error.vue @@ -11,6 +11,7 @@ const { data, error } = await useAsyncData(() => { }, { server: true }) if (error.value) { + useCookie('some-error').value = 'was set' throw createError({ statusCode: 422, fatal: true, statusMessage: 'This is a custom error' }) }