mirror of
https://github.com/nuxt/nuxt.git
synced 2024-11-11 00:23:53 +00:00
feat(nuxt): improve error dx for users (#4539)
Co-authored-by: Pooya Parsa <pooya@pi0.io>
This commit is contained in:
parent
1a862526fe
commit
78618f1f21
@ -80,13 +80,38 @@ This function will return the global Nuxt error that is being handled.
|
||||
::ReadMore{link="/api/composables/use-error"}
|
||||
::
|
||||
|
||||
### `throwError`
|
||||
### `createError`
|
||||
|
||||
* `function throwError (err: string | Error): Error`
|
||||
* `function createError (err: { cause, data, message, name, stack, statusCode, statusMessage, fatal }): Error`
|
||||
|
||||
You can call this function at any point on client-side, or (on server side) directly within middleware, plugins or `setup()` functions. It will trigger a full-screen error page (as above) which you can clear with `clearError`.
|
||||
You can use this function to create an error object with additional metadata. It is usable in both the Vue and Nitro portions of your app, and is meant to be thrown.
|
||||
|
||||
::ReadMore{link="/api/utils/throw-error"}
|
||||
If you throw an error created with `createError`:
|
||||
|
||||
* on server-side, it will trigger a full-screen error page which you can clear with `clearError`.
|
||||
* on client-side, it will throw a non-fatal error for you to handle. If you need to trigger a full-screen error page, then you can do this by setting `fatal: true`.
|
||||
|
||||
### Example
|
||||
|
||||
```vue [pages/movies/[slug].vue]
|
||||
<script setup>
|
||||
const route = useRoute()
|
||||
const { data } = await useFetch(`/api/movies/${route.params.slug}`)
|
||||
if (!data.value) {
|
||||
throw createError({ statusCode: 404, statusMessage: 'Page Not Found' })
|
||||
}
|
||||
</script>
|
||||
```
|
||||
|
||||
### `showError`
|
||||
|
||||
* `function showError (err: string | Error | { statusCode, statusMessage }): Error`
|
||||
|
||||
You can call this function at any point on client-side, or (on server side) directly within middleware, plugins or `setup()` functions. It will trigger a full-screen error page which you can clear with `clearError`.
|
||||
|
||||
It is recommended instead to use `throw createError()`.
|
||||
|
||||
::ReadMore{link="/api/utils/show-error"}
|
||||
::
|
||||
|
||||
### `clearError`
|
||||
|
44
docs/content/3.api/3.utils/create-error.md
Normal file
44
docs/content/3.api/3.utils/create-error.md
Normal file
@ -0,0 +1,44 @@
|
||||
# `createError`
|
||||
|
||||
You can use this function to create an error object with additional metadata. It is usable in both the Vue and Nitro portions of your app, and is meant to be thrown.
|
||||
|
||||
**Parameters:**
|
||||
|
||||
* err: { cause, data, message, name, stack, statusCode, statusMessage, fatal }
|
||||
|
||||
## Throwing errors in your Vue app
|
||||
|
||||
If you throw an error created with `createError`:
|
||||
|
||||
* on server-side, it will trigger a full-screen error page which you can clear with `clearError`.
|
||||
* on client-side, it will throw a non-fatal error for you to handle. If you need to trigger a full-screen error page, then you can do this by setting `fatal: true`.
|
||||
|
||||
### Example
|
||||
|
||||
```vue [pages/movies/[slug].vue]
|
||||
<script setup>
|
||||
const route = useRoute()
|
||||
const { data } = await useFetch(`/api/movies/${route.params.slug}`)
|
||||
if (!data.value) {
|
||||
throw createError({ statusCode: 404, statusMessage: 'Page Not Found' })
|
||||
}
|
||||
</script>
|
||||
```
|
||||
|
||||
## Throwing errors in API routes
|
||||
|
||||
You can use `createError` to trigger error handling in server API routes.
|
||||
|
||||
### Example
|
||||
|
||||
```js
|
||||
export default eventHandler(() => {
|
||||
throw createError({
|
||||
statusCode: 404,
|
||||
statusMessage: 'Page Not Found'
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
::ReadMore{link="/guide/features/error-handling"}
|
||||
::
|
21
docs/content/3.api/3.utils/show-error.md
Normal file
21
docs/content/3.api/3.utils/show-error.md
Normal file
@ -0,0 +1,21 @@
|
||||
# `showError`
|
||||
|
||||
Nuxt provides a quick and simple way to show a full screen error page if needed.
|
||||
|
||||
Within your pages, components and plugins you can use `showError` to show an error error.
|
||||
|
||||
**Parameters:**
|
||||
|
||||
- `error`: `string | Error | Partial<{ cause, data, message, name, stack, statusCode, statusMessage }>`
|
||||
|
||||
```js
|
||||
showError("😱 Oh no, an error has been thrown.")
|
||||
showError({ statusCode: 404, statusMessage: "Page Not Found" })
|
||||
```
|
||||
|
||||
The error is set in the state using [`useError()`](/api/composables/use-error) to create a reactive and SSR-friendly shared error state across components.
|
||||
|
||||
`showError` calls the `app:error` hook.
|
||||
|
||||
::ReadMore{link="/guide/features/error-handling"}
|
||||
::
|
@ -1,20 +0,0 @@
|
||||
# `throwError`
|
||||
|
||||
Nuxt provides a quick and simple way to throw errors.
|
||||
|
||||
Within your pages, components and plugins you can use `throwError` to throw an error.
|
||||
|
||||
**Parameters:**
|
||||
|
||||
- `error`: `string | Error`
|
||||
|
||||
```js
|
||||
throwError("😱 Oh no, an error has been thrown.")
|
||||
```
|
||||
|
||||
The thrown error is set in the state using [`useError()`](/api/composables/use-error) to create a reactive and SSR-friendly shared error state across components.
|
||||
|
||||
`throwError` calls the `app:error` hook.
|
||||
|
||||
::ReadMore{link="/guide/features/error-handling"}
|
||||
::
|
@ -1,5 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import { throwError } from '#app'
|
||||
import { showError } from '#app'
|
||||
const route = useRoute()
|
||||
if ('setup' in route.query) {
|
||||
throw new Error('error in setup')
|
||||
@ -30,7 +30,7 @@ function triggerError () {
|
||||
<NuxtLink to="/?middleware" class="n-link-base">
|
||||
Middleware
|
||||
</NuxtLink>
|
||||
<button class="n-link-base" @click="throwError">
|
||||
<button class="n-link-base" @click="showError">
|
||||
Trigger fatal error
|
||||
</button>
|
||||
<button class="n-link-base" @click="triggerError">
|
||||
|
@ -1,5 +1,5 @@
|
||||
export default defineNuxtRouteMiddleware((to) => {
|
||||
if ('middleware' in to.query) {
|
||||
return throwError('error in middleware')
|
||||
return showError('error in middleware')
|
||||
}
|
||||
})
|
||||
|
@ -7,7 +7,7 @@
|
||||
|
||||
<script setup>
|
||||
import { defineAsyncComponent, onErrorCaptured } from 'vue'
|
||||
import { callWithNuxt, throwError, useError, useNuxtApp } from '#app'
|
||||
import { callWithNuxt, isNuxtError, showError, useError, useNuxtApp } from '#app'
|
||||
|
||||
const ErrorComponent = defineAsyncComponent(() => import('#build/error-component.mjs'))
|
||||
|
||||
@ -24,8 +24,8 @@ if (process.dev && results && results.some(i => i && 'then' in i)) {
|
||||
const error = useError()
|
||||
onErrorCaptured((err, target, info) => {
|
||||
nuxtApp.hooks.callHook('vue:error', err, target, info).catch(hookError => console.error('[nuxt] Error in `vue:error` hook', hookError))
|
||||
if (process.server) {
|
||||
callWithNuxt(nuxtApp, throwError, [err])
|
||||
if (process.server || (isNuxtError(err) && (err.fatal || err.unhandled))) {
|
||||
callWithNuxt(nuxtApp, showError, [err])
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
@ -1,3 +1,4 @@
|
||||
import { createError as _createError, H3Error } from 'h3'
|
||||
import { useNuxtApp, useState } from '#app'
|
||||
|
||||
export const useError = () => {
|
||||
@ -5,20 +6,31 @@ export const useError = () => {
|
||||
return useState('error', () => process.server ? nuxtApp.ssrContext.error : nuxtApp.payload.error)
|
||||
}
|
||||
|
||||
export const throwError = (_err: string | Error) => {
|
||||
const nuxtApp = useNuxtApp()
|
||||
const error = useError()
|
||||
const err = typeof _err === 'string' ? new Error(_err) : _err
|
||||
nuxtApp.callHook('app:error', err)
|
||||
if (process.server) {
|
||||
nuxtApp.ssrContext.error = nuxtApp.ssrContext.error || err
|
||||
} else {
|
||||
error.value = error.value || err
|
||||
export interface NuxtError extends H3Error {}
|
||||
|
||||
export const showError = (_err: string | Error | Partial<NuxtError>) => {
|
||||
const err = createError(_err)
|
||||
err.fatal = true
|
||||
|
||||
try {
|
||||
const nuxtApp = useNuxtApp()
|
||||
nuxtApp.callHook('app:error', err)
|
||||
if (process.server) {
|
||||
nuxtApp.ssrContext.error = nuxtApp.ssrContext.error || err
|
||||
} else {
|
||||
const error = useError()
|
||||
error.value = error.value || err
|
||||
}
|
||||
} catch {
|
||||
throw err
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
/** @deprecated Use `throw createError()` or `showError` */
|
||||
export const throwError = showError
|
||||
|
||||
export const clearError = async (options: { redirect?: string } = {}) => {
|
||||
const nuxtApp = useNuxtApp()
|
||||
const error = useError()
|
||||
@ -28,3 +40,11 @@ export const clearError = async (options: { redirect?: string } = {}) => {
|
||||
}
|
||||
error.value = null
|
||||
}
|
||||
|
||||
export const isNuxtError = (err?: string | object): err is NuxtError => err && typeof err === 'object' && ('__nuxt_error' in err)
|
||||
|
||||
export const createError = (err: string | Partial<NuxtError>): NuxtError => {
|
||||
const _err: NuxtError = _createError(err)
|
||||
;(_err as any).__nuxt_error = true
|
||||
return _err
|
||||
}
|
||||
|
@ -3,7 +3,8 @@ export { useAsyncData, useLazyAsyncData, refreshNuxtData } from './asyncData'
|
||||
export type { AsyncDataOptions, AsyncData } from './asyncData'
|
||||
export { useHydration } from './hydrate'
|
||||
export { useState } from './state'
|
||||
export { clearError, throwError, useError } from './error'
|
||||
export { clearError, createError, isNuxtError, throwError, showError, useError } from './error'
|
||||
export type { NuxtError } from './error'
|
||||
export { useFetch, useLazyFetch } from './fetch'
|
||||
export type { FetchResult, UseFetchOptions } from './fetch'
|
||||
export { useCookie } from './cookie'
|
||||
|
@ -3,7 +3,7 @@ import { parseURL, parseQuery, withoutBase, isEqual, joinURL } from 'ufo'
|
||||
import { createError } from 'h3'
|
||||
import { defineNuxtPlugin } from '..'
|
||||
import { callWithNuxt } from '../nuxt'
|
||||
import { clearError, navigateTo, throwError, useRuntimeConfig } from '#app'
|
||||
import { clearError, navigateTo, showError, useRuntimeConfig } from '#app'
|
||||
// @ts-ignore
|
||||
import { globalMiddleware } from '#build/middleware'
|
||||
|
||||
@ -228,7 +228,7 @@ export default defineNuxtPlugin<{ route: Route, router: Router }>((nuxtApp) => {
|
||||
const error = result || createError({
|
||||
statusMessage: `Route navigation aborted: ${initialURL}`
|
||||
})
|
||||
return callWithNuxt(nuxtApp, throwError, [error])
|
||||
return callWithNuxt(nuxtApp, showError, [error])
|
||||
}
|
||||
}
|
||||
if (result || result === false) { return result }
|
||||
|
@ -43,8 +43,11 @@ export const appPreset = defineUnimportPreset({
|
||||
'abortNavigation',
|
||||
'addRouteMiddleware',
|
||||
'throwError',
|
||||
'showError',
|
||||
'clearError',
|
||||
'isNuxtError',
|
||||
'useError',
|
||||
'createError',
|
||||
'defineNuxtLink'
|
||||
]
|
||||
})
|
||||
|
@ -24,8 +24,8 @@ export default <NitroErrorHandler> async function errorhandler (_error, event) {
|
||||
event.res.statusMessage = errorObject.statusMessage
|
||||
|
||||
// Console output
|
||||
if (errorObject.statusCode !== 404) {
|
||||
console.error('[nuxt] [request error]', errorObject.message + '\n' + stack.map(l => ' ' + l.text).join(' \n'))
|
||||
if ((_error as any).unhandled) {
|
||||
console.error('[nuxt] [unhandled request error]', errorObject.message + '\n' + stack.map(l => ' ' + l.text).join(' \n'))
|
||||
}
|
||||
|
||||
// JSON response
|
||||
|
@ -9,7 +9,7 @@ import {
|
||||
import { createError } from 'h3'
|
||||
import { withoutBase, isEqual } from 'ufo'
|
||||
import NuxtPage from './page'
|
||||
import { callWithNuxt, defineNuxtPlugin, useRuntimeConfig, throwError, clearError, navigateTo, useError } from '#app'
|
||||
import { callWithNuxt, defineNuxtPlugin, useRuntimeConfig, showError, clearError, navigateTo, useError } from '#app'
|
||||
// @ts-ignore
|
||||
import routes from '#build/routes'
|
||||
// @ts-ignore
|
||||
@ -117,7 +117,7 @@ export default defineNuxtPlugin(async (nuxtApp) => {
|
||||
await router.isReady()
|
||||
} catch (error) {
|
||||
// We'll catch 404s here
|
||||
callWithNuxt(nuxtApp, throwError, [error])
|
||||
callWithNuxt(nuxtApp, showError, [error])
|
||||
}
|
||||
|
||||
router.beforeEach(async (to, from) => {
|
||||
@ -154,7 +154,7 @@ export default defineNuxtPlugin(async (nuxtApp) => {
|
||||
const error = result || createError({
|
||||
statusMessage: `Route navigation aborted: ${initialURL}`
|
||||
})
|
||||
return callWithNuxt(nuxtApp, throwError, [error])
|
||||
return callWithNuxt(nuxtApp, showError, [error])
|
||||
}
|
||||
}
|
||||
if (result || result === false) { return result }
|
||||
@ -169,7 +169,7 @@ export default defineNuxtPlugin(async (nuxtApp) => {
|
||||
await callWithNuxt(nuxtApp, clearError)
|
||||
}
|
||||
if (to.matched.length === 0) {
|
||||
callWithNuxt(nuxtApp, throwError, [createError({
|
||||
callWithNuxt(nuxtApp, showError, [createError({
|
||||
statusCode: 404,
|
||||
statusMessage: `Page not found: ${to.fullPath}`
|
||||
})])
|
||||
@ -192,7 +192,7 @@ export default defineNuxtPlugin(async (nuxtApp) => {
|
||||
})
|
||||
} catch (error) {
|
||||
// We'll catch middleware errors or deliberate exceptions here
|
||||
callWithNuxt(nuxtApp, throwError, [error])
|
||||
callWithNuxt(nuxtApp, showError, [error])
|
||||
}
|
||||
})
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user