mirror of
https://github.com/nuxt/nuxt.git
synced 2024-11-22 05:35:13 +00:00
fix(nuxt): ensure useError
is called with nuxt app context (#20585)
This commit is contained in:
parent
675445f98a
commit
16bf228437
@ -314,13 +314,13 @@ Be very careful before proxying headers to an external API and just include head
|
|||||||
If you want to pass on/proxy cookies in the other direction, from an internal request back to the client, you will need to handle this yourself.
|
If you want to pass on/proxy cookies in the other direction, from an internal request back to the client, you will need to handle this yourself.
|
||||||
|
|
||||||
```ts [composables/fetch.ts]
|
```ts [composables/fetch.ts]
|
||||||
import { appendHeader, H3Event } from 'h3'
|
import { appendResponseHeader, H3Event } from 'h3'
|
||||||
|
|
||||||
export const fetchWithCookie = async (event: H3Event, url: string) => {
|
export const fetchWithCookie = async (event: H3Event, url: string) => {
|
||||||
const res = await $fetch.raw(url)
|
const res = await $fetch.raw(url)
|
||||||
const cookies = (res.headers.get('set-cookie') || '').split(',')
|
const cookies = (res.headers.get('set-cookie') || '').split(',')
|
||||||
for (const cookie of cookies) {
|
for (const cookie of cookies) {
|
||||||
appendHeader(event, 'set-cookie', cookie)
|
appendResponseHeader(event, 'set-cookie', cookie)
|
||||||
}
|
}
|
||||||
return res._data
|
return res._data
|
||||||
}
|
}
|
||||||
|
@ -67,6 +67,10 @@ When you are ready to remove the error page, you can call the `clearError` helpe
|
|||||||
Make sure to check before using anything dependent on Nuxt plugins, such as `$route` or `useRouter`, as if a plugin threw an error, then it won't be re-run until you clear the error.
|
Make sure to check before using anything dependent on Nuxt plugins, such as `$route` or `useRouter`, as if a plugin threw an error, then it won't be re-run until you clear the error.
|
||||||
::
|
::
|
||||||
|
|
||||||
|
::alert{type="warning"}
|
||||||
|
If you are running on Node 16 and you set any cookies when rendering your error page, they will [overwrite cookies previously set](https://github.com/nuxt/nuxt/pull/20585). We recommend using a newer version of Node as Node 16 will reach end-of-life in September 2023.
|
||||||
|
::
|
||||||
|
|
||||||
### Example
|
### Example
|
||||||
|
|
||||||
```vue [error.vue]
|
```vue [error.vue]
|
||||||
|
@ -2,7 +2,7 @@ import type { RendererNode } from 'vue'
|
|||||||
import { computed, createStaticVNode, defineComponent, getCurrentInstance, h, ref, watch } from 'vue'
|
import { computed, createStaticVNode, defineComponent, getCurrentInstance, h, ref, watch } from 'vue'
|
||||||
import { debounce } from 'perfect-debounce'
|
import { debounce } from 'perfect-debounce'
|
||||||
import { hash } from 'ohash'
|
import { hash } from 'ohash'
|
||||||
import { appendHeader } from 'h3'
|
import { appendResponseHeader } from 'h3'
|
||||||
import { useHead } from '@unhead/vue'
|
import { useHead } from '@unhead/vue'
|
||||||
|
|
||||||
// eslint-disable-next-line import/no-restricted-paths
|
// eslint-disable-next-line import/no-restricted-paths
|
||||||
@ -42,7 +42,7 @@ export default defineComponent({
|
|||||||
const url = `/__nuxt_island/${props.name}:${hashId.value}`
|
const url = `/__nuxt_island/${props.name}:${hashId.value}`
|
||||||
if (process.server && process.env.prerender) {
|
if (process.server && process.env.prerender) {
|
||||||
// Hint to Nitro to prerender the island component
|
// Hint to Nitro to prerender the island component
|
||||||
appendHeader(event, 'x-nitro-prerender', url)
|
appendResponseHeader(event, 'x-nitro-prerender', url)
|
||||||
}
|
}
|
||||||
// TODO: Validate response
|
// TODO: Validate response
|
||||||
return $fetch<NuxtIslandResponse>(url, {
|
return $fetch<NuxtIslandResponse>(url, {
|
||||||
|
@ -2,7 +2,7 @@ import type { Ref } from 'vue'
|
|||||||
import { ref, watch } from 'vue'
|
import { ref, watch } from 'vue'
|
||||||
import type { CookieParseOptions, CookieSerializeOptions } from 'cookie-es'
|
import type { CookieParseOptions, CookieSerializeOptions } from 'cookie-es'
|
||||||
import { parse, serialize } from 'cookie-es'
|
import { parse, serialize } from 'cookie-es'
|
||||||
import { appendHeader } from 'h3'
|
import { appendResponseHeader } from 'h3'
|
||||||
import type { H3Event } from 'h3'
|
import type { H3Event } from 'h3'
|
||||||
import destr from 'destr'
|
import destr from 'destr'
|
||||||
import { isEqual } from 'ohash'
|
import { isEqual } from 'ohash'
|
||||||
@ -48,11 +48,10 @@ export function useCookie<T = string | null | undefined> (name: string, _opts?:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
const unhook = nuxtApp.hooks.hookOnce('app:rendered', writeFinalCookieValue)
|
const unhook = nuxtApp.hooks.hookOnce('app:rendered', writeFinalCookieValue)
|
||||||
const writeAndUnhook = () => {
|
nuxtApp.hooks.hookOnce('app:error', () => {
|
||||||
unhook() // don't write cookie subsequently when app:rendered is called
|
unhook() // don't write cookie subsequently when app:rendered is called
|
||||||
return writeFinalCookieValue()
|
return writeFinalCookieValue()
|
||||||
}
|
})
|
||||||
nuxtApp.hooks.hookOnce('app:error', writeAndUnhook)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return cookie as CookieRef<T>
|
return cookie as CookieRef<T>
|
||||||
@ -82,6 +81,6 @@ function writeClientCookie (name: string, value: any, opts: CookieSerializeOptio
|
|||||||
function writeServerCookie (event: H3Event, name: string, value: any, opts: CookieSerializeOptions = {}) {
|
function writeServerCookie (event: H3Event, name: string, value: any, opts: CookieSerializeOptions = {}) {
|
||||||
if (event) {
|
if (event) {
|
||||||
// TODO: Try to smart join with existing Set-Cookie headers
|
// TODO: Try to smart join with existing Set-Cookie headers
|
||||||
appendHeader(event, 'Set-Cookie', serializeCookie(name, value, opts))
|
appendResponseHeader(event, 'Set-Cookie', serializeCookie(name, value, opts))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -13,8 +13,10 @@ export const showError = (_err: string | Error | Partial<NuxtError>) => {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const nuxtApp = useNuxtApp()
|
const nuxtApp = useNuxtApp()
|
||||||
nuxtApp.callHook('app:error', err)
|
|
||||||
const error = useError()
|
const error = useError()
|
||||||
|
if (process.client) {
|
||||||
|
nuxtApp.hooks.callHook('app:error', err)
|
||||||
|
}
|
||||||
error.value = error.value || err
|
error.value = error.value || err
|
||||||
} catch {
|
} catch {
|
||||||
throw err
|
throw err
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { Fragment, computed, createStaticVNode, createVNode, defineComponent, h, ref, watch } from 'vue'
|
import { Fragment, computed, createStaticVNode, createVNode, defineComponent, h, ref, watch } from 'vue'
|
||||||
import { debounce } from 'perfect-debounce'
|
import { debounce } from 'perfect-debounce'
|
||||||
import { hash } from 'ohash'
|
import { hash } from 'ohash'
|
||||||
import { appendHeader } from 'h3'
|
import { appendResponseHeader } from 'h3'
|
||||||
|
|
||||||
import { useHead } from '@unhead/vue'
|
import { useHead } from '@unhead/vue'
|
||||||
import type { NuxtIslandResponse } from '../../core/runtime/nitro/renderer'
|
import type { NuxtIslandResponse } from '../../core/runtime/nitro/renderer'
|
||||||
@ -51,7 +51,7 @@ const NuxtServerComponent = defineComponent({
|
|||||||
const url = `/__nuxt_island/${props.name}:${hashId.value}`
|
const url = `/__nuxt_island/${props.name}:${hashId.value}`
|
||||||
if (process.server && process.env.prerender) {
|
if (process.server && process.env.prerender) {
|
||||||
// Hint to Nitro to prerender the island component
|
// Hint to Nitro to prerender the island component
|
||||||
appendHeader(event, 'x-nitro-prerender', url)
|
appendResponseHeader(event, 'x-nitro-prerender', url)
|
||||||
}
|
}
|
||||||
// TODO: Validate response
|
// TODO: Validate response
|
||||||
return $fetch<NuxtIslandResponse>(url, {
|
return $fetch<NuxtIslandResponse>(url, {
|
||||||
|
@ -2,7 +2,7 @@ import { createRenderer, renderResourceHeaders } from 'vue-bundle-renderer/runti
|
|||||||
import type { RenderResponse } from 'nitropack'
|
import type { RenderResponse } from 'nitropack'
|
||||||
import type { Manifest } from 'vite'
|
import type { Manifest } from 'vite'
|
||||||
import type { H3Event } from 'h3'
|
import type { H3Event } from 'h3'
|
||||||
import { appendHeader, createError, getQuery, readBody, writeEarlyHints } from 'h3'
|
import { appendResponseHeader, createError, getQuery, readBody, writeEarlyHints } from 'h3'
|
||||||
import devalue from '@nuxt/devalue'
|
import devalue from '@nuxt/devalue'
|
||||||
import { stringify, uneval } from 'devalue'
|
import { stringify, uneval } from 'devalue'
|
||||||
import destr from 'destr'
|
import destr from 'destr'
|
||||||
@ -275,7 +275,7 @@ export default defineRenderHandler(async (event): Promise<Partial<RenderResponse
|
|||||||
|
|
||||||
if (_PAYLOAD_EXTRACTION) {
|
if (_PAYLOAD_EXTRACTION) {
|
||||||
// Hint nitro to prerender payload for this route
|
// Hint nitro to prerender payload for this route
|
||||||
appendHeader(event, 'x-nitro-prerender', joinURL(url, process.env.NUXT_JSON_PAYLOADS ? '_payload.json' : '_payload.js'))
|
appendResponseHeader(event, 'x-nitro-prerender', joinURL(url, process.env.NUXT_JSON_PAYLOADS ? '_payload.json' : '_payload.js'))
|
||||||
// Use same ssr context to generate payload for this route
|
// Use same ssr context to generate payload for this route
|
||||||
PAYLOAD_CACHE!.set(withoutTrailingSlash(url), renderPayloadResponse(ssrContext))
|
PAYLOAD_CACHE!.set(withoutTrailingSlash(url), renderPayloadResponse(ssrContext))
|
||||||
}
|
}
|
||||||
|
@ -114,8 +114,9 @@ describe('pages', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('validates routes', async () => {
|
it('validates routes', async () => {
|
||||||
const { status } = await fetch('/forbidden')
|
const { status, headers } = await fetch('/forbidden')
|
||||||
expect(status).toEqual(404)
|
expect(status).toEqual(404)
|
||||||
|
expect(headers.get('Set-Cookie')).toBe('set-in-plugin=true; Path=/')
|
||||||
|
|
||||||
const page = await createPage('/navigate-to-forbidden')
|
const page = await createPage('/navigate-to-forbidden')
|
||||||
await page.waitForLoadState('networkidle')
|
await page.waitForLoadState('networkidle')
|
||||||
@ -135,8 +136,11 @@ describe('pages', () => {
|
|||||||
expect(status).toEqual(500)
|
expect(status).toEqual(500)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('render 404', async () => {
|
it('render catchall page', async () => {
|
||||||
const html = await $fetch('/not-found')
|
const res = await fetch('/not-found')
|
||||||
|
expect(res.status).toEqual(200)
|
||||||
|
|
||||||
|
const html = await res.text()
|
||||||
|
|
||||||
// Snapshot
|
// Snapshot
|
||||||
// expect(html).toMatchInlineSnapshot()
|
// expect(html).toMatchInlineSnapshot()
|
||||||
@ -578,7 +582,9 @@ describe('errors', () => {
|
|||||||
|
|
||||||
it('should render a HTML error page', async () => {
|
it('should render a HTML error page', async () => {
|
||||||
const res = await fetch('/error')
|
const res = await fetch('/error')
|
||||||
expect(res.headers.get('Set-Cookie')).toBe('some-error=was%20set; Path=/')
|
expect(res.headers.get('Set-Cookie')).toBe('set-in-plugin=true; Path=/')
|
||||||
|
// TODO: enable when we update test to node v16
|
||||||
|
// expect(res.headers.get('Set-Cookie')).toBe('set-in-plugin=true; Path=/, some-error=was%20set; Path=/')
|
||||||
expect(await res.text()).toContain('This is a custom error')
|
expect(await res.text()).toContain('This is a custom error')
|
||||||
})
|
})
|
||||||
|
|
||||||
|
3
test/fixtures/basic/plugins/cookie.ts
vendored
Normal file
3
test/fixtures/basic/plugins/cookie.ts
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
export default defineNuxtPlugin(() => {
|
||||||
|
useCookie('set-in-plugin').value = 'true'
|
||||||
|
})
|
Loading…
Reference in New Issue
Block a user