From 04bceb9d18550ee81b0085de44a66c468098565a Mon Sep 17 00:00:00 2001 From: Daniel Roe Date: Tue, 25 Apr 2023 13:42:24 +0100 Subject: [PATCH 1/6] fix(nuxt): return `RenderResponse` for redirects --- packages/nuxt/src/app/composables/cookie.ts | 7 +----- packages/nuxt/src/app/composables/router.ts | 25 +++++++++++++------ packages/nuxt/src/app/nuxt.ts | 3 +++ .../nuxt/src/core/runtime/nitro/renderer.ts | 23 ++++++++++------- test/bundle.test.ts | 2 +- 5 files changed, 36 insertions(+), 24 deletions(-) diff --git a/packages/nuxt/src/app/composables/cookie.ts b/packages/nuxt/src/app/composables/cookie.ts index e36957a34350..59e87db66e49 100644 --- a/packages/nuxt/src/app/composables/cookie.ts +++ b/packages/nuxt/src/app/composables/cookie.ts @@ -47,12 +47,7 @@ export function useCookie (name: string, _opts?: writeServerCookie(useRequestEvent(nuxtApp), name, cookie.value, 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() - return writeFinalCookieValue() - }) + nuxtApp.hooks.hookOnce('app:rendered', writeFinalCookieValue) } return cookie as CookieRef diff --git a/packages/nuxt/src/app/composables/router.ts b/packages/nuxt/src/app/composables/router.ts index 817265a7fb79..3392e3d3a8b1 100644 --- a/packages/nuxt/src/app/composables/router.ts +++ b/packages/nuxt/src/app/composables/router.ts @@ -1,7 +1,7 @@ import { getCurrentInstance, inject, onUnmounted } from 'vue' import type { Ref } from 'vue' import type { NavigationFailure, NavigationGuard, RouteLocationNormalized, RouteLocationNormalizedLoaded, RouteLocationPathRaw, RouteLocationRaw, Router } from 'vue-router' -import { sendRedirect } from 'h3' +import { sanitizeStatusCode } from 'h3' import { hasProtocol, joinURL, parseURL } from 'ufo' import { useNuxtApp, useRuntimeConfig } from '../nuxt' @@ -86,7 +86,7 @@ export interface NavigateToOptions { external?: boolean } -export const navigateTo = (to: RouteLocationRaw | undefined | null, options?: NavigateToOptions): Promise | RouteLocationRaw => { +export const navigateTo = (to: RouteLocationRaw | undefined | null, options?: NavigateToOptions): Promise | false | void | RouteLocationRaw => { if (!to) { to = '/' } @@ -113,13 +113,22 @@ export const navigateTo = (to: RouteLocationRaw | undefined | null, options?: Na const nuxtApp = useNuxtApp() if (nuxtApp.ssrContext && nuxtApp.ssrContext.event) { const fullPath = typeof to === 'string' || isExternal ? toPath : router.resolve(to).fullPath || '/' - const redirectLocation = isExternal ? toPath : joinURL(useRuntimeConfig().app.baseURL, fullPath) - const redirect = () => nuxtApp.callHook('app:redirected') - .then(() => sendRedirect(nuxtApp.ssrContext!.event, redirectLocation, options?.redirectCode || 302)) - .then(() => inMiddleware ? /* abort route navigation */ false : undefined) + const location = isExternal ? toPath : joinURL(useRuntimeConfig().app.baseURL, fullPath) + + async function redirect () { + // TODO: consider deprecating in favour of `app:rendered` and removing + await nuxtApp.callHook('app:redirected') + const encodedLoc = location.replace(/"/g, '%22') + nuxtApp.ssrContext!._renderResponse = { + statusCode: sanitizeStatusCode(options?.redirectCode || 302, nuxtApp.ssrContext!.event.node.res.statusCode), + body: ``, + headers: { location } + } + return inMiddleware ? /* abort route navigation */ false : undefined + } - // We wait to perform the redirect in case any other middleware will intercept the redirect - // and redirect further. + // We wait to perform the redirect last in case any other middleware will intercept the redirect + // and redirect somewhere else instead. if (!isExternal && inMiddleware) { router.beforeEach(final => (final.fullPath === fullPath) ? redirect() : undefined) return to diff --git a/packages/nuxt/src/app/nuxt.ts b/packages/nuxt/src/app/nuxt.ts index fc239088b0cd..21aa34d890a5 100644 --- a/packages/nuxt/src/app/nuxt.ts +++ b/packages/nuxt/src/app/nuxt.ts @@ -8,6 +8,7 @@ import { getContext } from 'unctx' import type { SSRContext } from 'vue-bundle-renderer/runtime' import type { H3Event } from 'h3' import type { AppConfig, AppConfigInput, RuntimeConfig } from 'nuxt/schema' +import type { RenderResponse } from 'nitropack' // eslint-disable-next-line import/no-restricted-paths import type { NuxtIslandContext } from '../core/runtime/nitro/renderer' @@ -61,6 +62,8 @@ export interface NuxtSSRContext extends SSRContext { renderMeta?: () => Promise | NuxtMeta islandContext?: NuxtIslandContext /** @internal */ + _renderResponse?: Partial + /** @internal */ _payloadReducers: Record any> } diff --git a/packages/nuxt/src/core/runtime/nitro/renderer.ts b/packages/nuxt/src/core/runtime/nitro/renderer.ts index 7ea3a73fd222..2c05fe7675c5 100644 --- a/packages/nuxt/src/core/runtime/nitro/renderer.ts +++ b/packages/nuxt/src/core/runtime/nitro/renderer.ts @@ -167,7 +167,7 @@ const ROOT_NODE_REGEX = new RegExp(`^<${appRootTag} id="${appRootId}">([\\s\\S]* const PRERENDER_NO_SSR_ROUTES = new Set(['/index.html', '/200.html', '/404.html']) -export default defineRenderHandler(async (event) => { +export default defineRenderHandler(async (event): Promise> => { const nitroApp = useNitroApp() // Whether we're rendering an error page @@ -247,7 +247,12 @@ export default defineRenderHandler(async (event) => { }) await ssrContext.nuxt?.hooks.callHook('app:rendered', { ssrContext }) - if (event.node.res.headersSent || event.node.res.writableEnded) { return } + if (ssrContext._renderResponse) { return ssrContext._renderResponse } + + if (event.node.res.headersSent || event.node.res.writableEnded) { + // @ts-expect-error TODO: handle additional cases + return + } // Handle errors if (ssrContext.payload?.error && !ssrError) { @@ -304,13 +309,13 @@ export default defineRenderHandler(async (event) => { NO_SCRIPTS ? undefined : (_PAYLOAD_EXTRACTION - ? process.env.NUXT_JSON_PAYLOADS - ? renderPayloadJsonScript({ id: '__NUXT_DATA__', ssrContext, data: splitPayload(ssrContext).initial, src: payloadURL }) - : renderPayloadScript({ ssrContext, data: splitPayload(ssrContext).initial, src: payloadURL }) - : process.env.NUXT_JSON_PAYLOADS - ? renderPayloadJsonScript({ id: '__NUXT_DATA__', ssrContext, data: ssrContext.payload }) - : renderPayloadScript({ ssrContext, data: ssrContext.payload }) - ), + ? process.env.NUXT_JSON_PAYLOADS + ? renderPayloadJsonScript({ id: '__NUXT_DATA__', ssrContext, data: splitPayload(ssrContext).initial, src: payloadURL }) + : renderPayloadScript({ ssrContext, data: splitPayload(ssrContext).initial, src: payloadURL }) + : process.env.NUXT_JSON_PAYLOADS + ? renderPayloadJsonScript({ id: '__NUXT_DATA__', ssrContext, data: ssrContext.payload }) + : renderPayloadScript({ ssrContext, data: ssrContext.payload }) + ), routeOptions.experimentalNoScripts ? undefined : _rendered.renderScripts(), // Note: bodyScripts may contain tags other than diff --git a/test/fixtures/basic/pages/navigate-to-redirect.vue b/test/fixtures/basic/pages/navigate-to-redirect.vue index 9e934988b79c..f9a3a59eb583 100644 --- a/test/fixtures/basic/pages/navigate-to-redirect.vue +++ b/test/fixtures/basic/pages/navigate-to-redirect.vue @@ -9,4 +9,8 @@ definePageMeta({ return navigateTo({ path: '/' }, { redirectCode: 307 }) } }) +console.log('running setup') +useNuxtApp().hook('app:rendered', () => { + throw new Error('this should not run') +}) From 40facd4a87fba6dee741cdbe6c3f5970715496ae Mon Sep 17 00:00:00 2001 From: Daniel Roe Date: Tue, 25 Apr 2023 14:15:20 +0100 Subject: [PATCH 4/6] style: indent --- packages/nuxt/src/core/runtime/nitro/renderer.ts | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/nuxt/src/core/runtime/nitro/renderer.ts b/packages/nuxt/src/core/runtime/nitro/renderer.ts index 2c05fe7675c5..ed10f2ab6243 100644 --- a/packages/nuxt/src/core/runtime/nitro/renderer.ts +++ b/packages/nuxt/src/core/runtime/nitro/renderer.ts @@ -309,13 +309,13 @@ export default defineRenderHandler(async (event): Promise renderedMeta.bodyScripts From cbda3c5e4937962b6dcc0d20118352c771936480 Mon Sep 17 00:00:00 2001 From: Daniel Roe Date: Tue, 25 Apr 2023 14:21:25 +0100 Subject: [PATCH 5/6] test: update type snapshot --- test/fixtures/basic/types.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/fixtures/basic/types.ts b/test/fixtures/basic/types.ts index 15985c74965e..b3403c949f6f 100644 --- a/test/fixtures/basic/types.ts +++ b/test/fixtures/basic/types.ts @@ -96,7 +96,7 @@ describe('middleware', () => { addRouteMiddleware('example', (to, from) => { expectTypeOf(to).toEqualTypeOf() expectTypeOf(from).toEqualTypeOf() - expectTypeOf(navigateTo).toEqualTypeOf<(to: RouteLocationRaw | null | undefined, options?: NavigateToOptions) => RouteLocationRaw | Promise>() + expectTypeOf(navigateTo).toEqualTypeOf < (to: RouteLocationRaw | null | undefined, options ?: NavigateToOptions) => RouteLocationRaw | void | false | Promise>() navigateTo('/') abortNavigation() abortNavigation('error string') From a4dab2b661df56b5316f2cd692dc068331f3162a Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Tue, 25 Apr 2023 13:23:04 +0000 Subject: [PATCH 6/6] [autofix.ci] apply automated fixes --- test/fixtures/basic/types.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/fixtures/basic/types.ts b/test/fixtures/basic/types.ts index b3403c949f6f..8cc65416c5d4 100644 --- a/test/fixtures/basic/types.ts +++ b/test/fixtures/basic/types.ts @@ -96,7 +96,7 @@ describe('middleware', () => { addRouteMiddleware('example', (to, from) => { expectTypeOf(to).toEqualTypeOf() expectTypeOf(from).toEqualTypeOf() - expectTypeOf(navigateTo).toEqualTypeOf < (to: RouteLocationRaw | null | undefined, options ?: NavigateToOptions) => RouteLocationRaw | void | false | Promise>() + expectTypeOf(navigateTo).toEqualTypeOf <(to: RouteLocationRaw | null | undefined, options ?: NavigateToOptions) => RouteLocationRaw | void | false | Promise>() navigateTo('/') abortNavigation() abortNavigation('error string')