mirror of
https://github.com/nuxt/nuxt.git
synced 2024-11-29 00:52:01 +00:00
fix(nuxt): return RenderResponse
for redirects (#20496)
This commit is contained in:
parent
98b20c45c8
commit
f73bb1de0a
@ -27,7 +27,7 @@ const CookieDefaults: CookieOptions<any> = {
|
|||||||
encode: val => encodeURIComponent(typeof val === 'string' ? val : JSON.stringify(val))
|
encode: val => encodeURIComponent(typeof val === 'string' ? val : JSON.stringify(val))
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useCookie <T = string | null | undefined> (name: string, _opts?: CookieOptions<T>): CookieRef<T> {
|
export function useCookie<T = string | null | undefined> (name: string, _opts?: CookieOptions<T>): CookieRef<T> {
|
||||||
const opts = { ...CookieDefaults, ..._opts }
|
const opts = { ...CookieDefaults, ..._opts }
|
||||||
const cookies = readRawCookies(opts) || {}
|
const cookies = readRawCookies(opts) || {}
|
||||||
|
|
||||||
@ -53,7 +53,6 @@ export function useCookie <T = string | null | undefined> (name: string, _opts?:
|
|||||||
return writeFinalCookieValue()
|
return writeFinalCookieValue()
|
||||||
}
|
}
|
||||||
nuxtApp.hooks.hookOnce('app:error', writeAndUnhook)
|
nuxtApp.hooks.hookOnce('app:error', writeAndUnhook)
|
||||||
nuxtApp.hooks.hookOnce('app:redirected', writeAndUnhook)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return cookie as CookieRef<T>
|
return cookie as CookieRef<T>
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { getCurrentInstance, inject, onUnmounted } from 'vue'
|
import { getCurrentInstance, inject, onUnmounted } from 'vue'
|
||||||
import type { Ref } from 'vue'
|
import type { Ref } from 'vue'
|
||||||
import type { NavigationFailure, NavigationGuard, RouteLocationNormalized, RouteLocationNormalizedLoaded, RouteLocationPathRaw, RouteLocationRaw, Router } from 'vue-router'
|
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 { hasProtocol, joinURL, parseURL } from 'ufo'
|
||||||
|
|
||||||
import { useNuxtApp, useRuntimeConfig } from '../nuxt'
|
import { useNuxtApp, useRuntimeConfig } from '../nuxt'
|
||||||
@ -86,7 +86,7 @@ export interface NavigateToOptions {
|
|||||||
external?: boolean
|
external?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export const navigateTo = (to: RouteLocationRaw | undefined | null, options?: NavigateToOptions): Promise<void | NavigationFailure | false> | RouteLocationRaw => {
|
export const navigateTo = (to: RouteLocationRaw | undefined | null, options?: NavigateToOptions): Promise<void | NavigationFailure | false> | false | void | RouteLocationRaw => {
|
||||||
if (!to) {
|
if (!to) {
|
||||||
to = '/'
|
to = '/'
|
||||||
}
|
}
|
||||||
@ -111,17 +111,26 @@ export const navigateTo = (to: RouteLocationRaw | undefined | null, options?: Na
|
|||||||
|
|
||||||
if (process.server) {
|
if (process.server) {
|
||||||
const nuxtApp = useNuxtApp()
|
const nuxtApp = useNuxtApp()
|
||||||
if (nuxtApp.ssrContext && nuxtApp.ssrContext.event) {
|
if (nuxtApp.ssrContext) {
|
||||||
const fullPath = typeof to === 'string' || isExternal ? toPath : router.resolve(to).fullPath || '/'
|
const fullPath = typeof to === 'string' || isExternal ? toPath : router.resolve(to).fullPath || '/'
|
||||||
const redirectLocation = isExternal ? toPath : joinURL(useRuntimeConfig().app.baseURL, fullPath)
|
const location = 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)
|
|
||||||
|
|
||||||
// We wait to perform the redirect in case any other middleware will intercept the redirect
|
async function redirect () {
|
||||||
// and redirect further.
|
// 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, 302),
|
||||||
|
body: `<!DOCTYPE html><html><head><meta http-equiv="refresh" content="0; url=${encodedLoc}"></head></html>`,
|
||||||
|
headers: { location }
|
||||||
|
}
|
||||||
|
return inMiddleware ? /* abort route navigation */ false : undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
// We wait to perform the redirect last in case any other middleware will intercept the redirect
|
||||||
|
// and redirect somewhere else instead.
|
||||||
if (!isExternal && inMiddleware) {
|
if (!isExternal && inMiddleware) {
|
||||||
router.beforeEach(final => (final.fullPath === fullPath) ? redirect() : undefined)
|
router.afterEach(final => (final.fullPath === fullPath) ? redirect() : undefined)
|
||||||
return to
|
return to
|
||||||
}
|
}
|
||||||
return redirect()
|
return redirect()
|
||||||
|
@ -8,6 +8,7 @@ import { getContext } from 'unctx'
|
|||||||
import type { SSRContext } from 'vue-bundle-renderer/runtime'
|
import type { SSRContext } from 'vue-bundle-renderer/runtime'
|
||||||
import type { H3Event } from 'h3'
|
import type { H3Event } from 'h3'
|
||||||
import type { AppConfig, AppConfigInput, RuntimeConfig } from 'nuxt/schema'
|
import type { AppConfig, AppConfigInput, RuntimeConfig } from 'nuxt/schema'
|
||||||
|
import type { RenderResponse } from 'nitropack'
|
||||||
|
|
||||||
// eslint-disable-next-line import/no-restricted-paths
|
// eslint-disable-next-line import/no-restricted-paths
|
||||||
import type { NuxtIslandContext } from '../core/runtime/nitro/renderer'
|
import type { NuxtIslandContext } from '../core/runtime/nitro/renderer'
|
||||||
@ -61,6 +62,8 @@ export interface NuxtSSRContext extends SSRContext {
|
|||||||
renderMeta?: () => Promise<NuxtMeta> | NuxtMeta
|
renderMeta?: () => Promise<NuxtMeta> | NuxtMeta
|
||||||
islandContext?: NuxtIslandContext
|
islandContext?: NuxtIslandContext
|
||||||
/** @internal */
|
/** @internal */
|
||||||
|
_renderResponse?: Partial<RenderResponse>
|
||||||
|
/** @internal */
|
||||||
_payloadReducers: Record<string, (data: any) => any>
|
_payloadReducers: Record<string, (data: any) => any>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5,7 +5,6 @@ import { callWithNuxt, defineNuxtPlugin, useRuntimeConfig } from '../nuxt'
|
|||||||
import { clearError, showError } from '../composables/error'
|
import { clearError, showError } from '../composables/error'
|
||||||
import { navigateTo } from '../composables/router'
|
import { navigateTo } from '../composables/router'
|
||||||
import { useState } from '../composables/state'
|
import { useState } from '../composables/state'
|
||||||
import { useRequestEvent } from '../composables/ssr'
|
|
||||||
|
|
||||||
// @ts-expect-error virtual file
|
// @ts-expect-error virtual file
|
||||||
import { globalMiddleware } from '#build/middleware'
|
import { globalMiddleware } from '#build/middleware'
|
||||||
@ -258,9 +257,7 @@ export default defineNuxtPlugin<{ route: Route, router: Router }>({
|
|||||||
|
|
||||||
await router.replace(initialURL)
|
await router.replace(initialURL)
|
||||||
if (!isEqual(route.fullPath, initialURL)) {
|
if (!isEqual(route.fullPath, initialURL)) {
|
||||||
const event = await callWithNuxt(nuxtApp, useRequestEvent)
|
await callWithNuxt(nuxtApp, navigateTo, [route.fullPath])
|
||||||
const options = { redirectCode: event.node.res.statusCode !== 200 ? event.node.res.statusCode || 302 : 302 }
|
|
||||||
await callWithNuxt(nuxtApp, navigateTo, [route.fullPath, options])
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -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'])
|
const PRERENDER_NO_SSR_ROUTES = new Set(['/index.html', '/200.html', '/404.html'])
|
||||||
|
|
||||||
export default defineRenderHandler(async (event) => {
|
export default defineRenderHandler(async (event): Promise<Partial<RenderResponse>> => {
|
||||||
const nitroApp = useNitroApp()
|
const nitroApp = useNitroApp()
|
||||||
|
|
||||||
// Whether we're rendering an error page
|
// Whether we're rendering an error page
|
||||||
@ -252,7 +252,12 @@ export default defineRenderHandler(async (event) => {
|
|||||||
})
|
})
|
||||||
await ssrContext.nuxt?.hooks.callHook('app:rendered', { ssrContext })
|
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
|
// Handle errors
|
||||||
if (ssrContext.payload?.error && !ssrError) {
|
if (ssrContext.payload?.error && !ssrError) {
|
||||||
|
@ -34,7 +34,7 @@ describe.skipIf(isWindows || process.env.TEST_BUILDER === 'webpack' || process.e
|
|||||||
|
|
||||||
it('default client bundle size', async () => {
|
it('default client bundle size', async () => {
|
||||||
stats.client = await analyzeSizes('**/*.js', publicDir)
|
stats.client = await analyzeSizes('**/*.js', publicDir)
|
||||||
expect(roundToKilobytes(stats.client.totalBytes)).toMatchInlineSnapshot('"94.3k"')
|
expect(roundToKilobytes(stats.client.totalBytes)).toMatchInlineSnapshot('"94.2k"')
|
||||||
expect(stats.client.files.map(f => f.replace(/\..*\.js/, '.js'))).toMatchInlineSnapshot(`
|
expect(stats.client.files.map(f => f.replace(/\..*\.js/, '.js'))).toMatchInlineSnapshot(`
|
||||||
[
|
[
|
||||||
"_nuxt/entry.js",
|
"_nuxt/entry.js",
|
||||||
|
@ -3,5 +3,10 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
|
if (useRoute().path === '/navigate-to-external') {
|
||||||
|
useNuxtApp().hook('app:rendered', () => {
|
||||||
|
throw new Error('this should not run')
|
||||||
|
})
|
||||||
|
}
|
||||||
await navigateTo('https://example.com/', { external: true, replace: true })
|
await navigateTo('https://example.com/', { external: true, replace: true })
|
||||||
</script>
|
</script>
|
||||||
|
@ -9,4 +9,8 @@ definePageMeta({
|
|||||||
return navigateTo({ path: '/' }, { redirectCode: 307 })
|
return navigateTo({ path: '/' }, { redirectCode: 307 })
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
console.log('running setup')
|
||||||
|
useNuxtApp().hook('app:rendered', () => {
|
||||||
|
throw new Error('this should not run')
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
2
test/fixtures/basic/types.ts
vendored
2
test/fixtures/basic/types.ts
vendored
@ -96,7 +96,7 @@ describe('middleware', () => {
|
|||||||
addRouteMiddleware('example', (to, from) => {
|
addRouteMiddleware('example', (to, from) => {
|
||||||
expectTypeOf(to).toEqualTypeOf<RouteLocationNormalizedLoaded>()
|
expectTypeOf(to).toEqualTypeOf<RouteLocationNormalizedLoaded>()
|
||||||
expectTypeOf(from).toEqualTypeOf<RouteLocationNormalizedLoaded>()
|
expectTypeOf(from).toEqualTypeOf<RouteLocationNormalizedLoaded>()
|
||||||
expectTypeOf(navigateTo).toEqualTypeOf<(to: RouteLocationRaw | null | undefined, options?: NavigateToOptions) => RouteLocationRaw | Promise<void | NavigationFailure | false>>()
|
expectTypeOf(navigateTo).toEqualTypeOf <(to: RouteLocationRaw | null | undefined, options ?: NavigateToOptions) => RouteLocationRaw | void | false | Promise<void | NavigationFailure | false>>()
|
||||||
navigateTo('/')
|
navigateTo('/')
|
||||||
abortNavigation()
|
abortNavigation()
|
||||||
abortNavigation('error string')
|
abortNavigation('error string')
|
||||||
|
Loading…
Reference in New Issue
Block a user