Skip to content
This repository was archived by the owner on Apr 6, 2023. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
54 commits
Select commit Hold shift + click to select a range
6545667
feat(nuxt): support creating errors directly from `throwError`
danielroe Apr 22, 2022
ab3bca3
fix(nuxt): clear errors _after_ route resolves
danielroe Apr 22, 2022
1481e00
docs: update documentation
danielroe Apr 22, 2022
8b60613
Merge remote-tracking branch 'origin/main' into fix/error-dx
danielroe Apr 22, 2022
4c899c7
style: spacing
danielroe Apr 22, 2022
3b205ca
fix: don't re-create errors
danielroe Apr 22, 2022
5a8bcb6
Update docs/content/2.guide/2.features/7.error-handling.md
danielroe Apr 22, 2022
d224281
Update docs/content/3.api/3.utils/throw-error.md
danielroe Apr 22, 2022
727e585
Update docs/content/2.guide/2.features/7.error-handling.md
danielroe Apr 22, 2022
3983e87
Update packages/nuxt/src/app/composables/error.ts
danielroe Apr 22, 2022
75466e6
docs: check error value
danielroe Apr 22, 2022
a4af259
Merge remote-tracking branch 'origin/main' into fix/error-dx
danielroe Apr 22, 2022
358f914
Merge remote-tracking branch 'origin/main' into fix/error-dx
danielroe May 3, 2022
5f4a112
fix: restore code missed in merge
danielroe May 3, 2022
aede51c
fix: support `throw createError()` directly
danielroe May 3, 2022
8426fd5
Merge branch 'main' into fix/error-dx
danielroe May 6, 2022
b751402
Merge remote-tracking branch 'origin/main' into fix/error-dx
danielroe May 6, 2022
ab4c4d5
Merge branch 'main' into fix/error-dx
danielroe May 12, 2022
8c7849d
fix: export/auto-import `createError` from composables
danielroe May 13, 2022
b0b5a91
fix: implement fuller wrapper
danielroe May 13, 2022
5a289b9
Merge remote-tracking branch 'origin/main' into fix/error-dx
danielroe May 13, 2022
6ffa080
fix: export `createError`
danielroe May 13, 2022
912ab0a
Merge branch 'main' into fix/error-dx
danielroe May 25, 2022
6b9131b
Merge branch 'main' into fix/error-dx
danielroe May 31, 2022
71fbf23
Merge remote-tracking branch 'origin/main' into fix/error-dx
danielroe Jun 8, 2022
d846c00
docs: show underlying options for error
danielroe Jun 8, 2022
2124e3f
fix: move checks into `createError`
danielroe Jun 8, 2022
31a6b06
Merge branch 'main' into fix/error-dx
danielroe Jun 10, 2022
bbafd5e
fix: use `isError` from `h3` rather than instanceof
danielroe Jun 10, 2022
09fc189
docs: remove createError import
danielroe Jun 10, 2022
1e75c8c
refactor: isNuxtError
danielroe Jun 10, 2022
8e902b7
Merge remote-tracking branch 'origin/main' into fix/error-dx
danielroe Jun 10, 2022
88a42fa
docs: add blank `createError` page
danielroe Jun 10, 2022
18ebba3
fix: use native createError support for strings
danielroe Jun 10, 2022
711f4ec
Merge remote-tracking branch 'origin/main' into fix/error-dx
danielroe Jun 15, 2022
a82450a
fix: set default `statusMessage` if one is not provided
danielroe Jun 15, 2022
8bdcee9
chore: upgrade h3
danielroe Jun 17, 2022
1713806
Merge branch 'main' into fix/error-dx
danielroe Jun 17, 2022
5720255
chore: dedupe h3
danielroe Jun 17, 2022
6ce6521
Merge remote-tracking branch 'origin/main' into fix/error-dx
danielroe Jun 22, 2022
3d66ed7
chore: dedupe
danielroe Jun 22, 2022
95852e5
chore: dedupe for real
danielroe Jun 22, 2022
aa8086b
Merge remote-tracking branch 'origin/main' into fix/error-dx
danielroe Jul 4, 2022
0dfe1e5
fix: auto import `isNuxtError`
danielroe Jul 4, 2022
bfb4a08
Merge branch 'main' into fix/error-dx
pi0 Jul 7, 2022
f72cff3
fix: errors are not fatal by default on client-side unless `fatal: true`
danielroe Jul 21, 2022
2cce81a
Merge remote-tracking branch 'origin/main' into fix/error-dx
danielroe Jul 21, 2022
992a0f7
docs: remove stray wor
danielroe Jul 21, 2022
9bba606
refactor: rename `throwError` -> `showError`, deprecating old name
danielroe Jul 21, 2022
5aee7cf
Merge branch 'main' into fix/error-dx
pi0 Jul 21, 2022
f61e13c
upgrade h3 and reuse fatal
pi0 Jul 21, 2022
b04dda0
only log unhandled errors
pi0 Jul 21, 2022
e69bdf8
update ui templates
pi0 Jul 21, 2022
444a59c
Merge branch 'main' into fix/error-dx
pi0 Jul 21, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 29 additions & 4 deletions docs/content/2.guide/2.features/7.error-handling.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`
Expand Down
44 changes: 44 additions & 0 deletions docs/content/3.api/3.utils/create-error.md
Original file line number Diff line number Diff line change
@@ -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 changes: 21 additions & 0 deletions docs/content/3.api/3.utils/show-error.md
Original file line number Diff line number Diff line change
@@ -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"}
::
20 changes: 0 additions & 20 deletions docs/content/3.api/3.utils/throw-error.md

This file was deleted.

4 changes: 2 additions & 2 deletions examples/app/error-handling/app.vue
Original file line number Diff line number Diff line change
@@ -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')
Expand Down Expand Up @@ -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">
Expand Down
2 changes: 1 addition & 1 deletion examples/app/error-handling/middleware/error.global.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
export default defineNuxtRouteMiddleware((to) => {
if ('middleware' in to.query) {
return throwError('error in middleware')
return showError('error in middleware')
}
})
6 changes: 3 additions & 3 deletions packages/nuxt/src/app/components/nuxt-root.vue
Original file line number Diff line number Diff line change
Expand Up @@ -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'))

Expand All @@ -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>
38 changes: 29 additions & 9 deletions packages/nuxt/src/app/composables/error.ts
Original file line number Diff line number Diff line change
@@ -1,24 +1,36 @@
import { createError as _createError, H3Error } from 'h3'
import { useNuxtApp, useState } from '#app'

export const useError = () => {
const nuxtApp = useNuxtApp()
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()
Expand All @@ -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 changes: 2 additions & 1 deletion packages/nuxt/src/app/composables/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down
4 changes: 2 additions & 2 deletions packages/nuxt/src/app/plugins/router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'

Expand Down Expand Up @@ -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 }
Expand Down
3 changes: 3 additions & 0 deletions packages/nuxt/src/auto-imports/presets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,11 @@ export const appPreset = defineUnimportPreset({
'abortNavigation',
'addRouteMiddleware',
'throwError',
'showError',
'clearError',
'isNuxtError',
'useError',
'createError',
'defineNuxtLink'
]
})
Expand Down
4 changes: 2 additions & 2 deletions packages/nuxt/src/core/runtime/nitro/error.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
10 changes: 5 additions & 5 deletions packages/nuxt/src/pages/runtime/router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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) => {
Expand Down Expand Up @@ -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 }
Expand All @@ -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}`
})])
Expand All @@ -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])
}
})

Expand Down