Skip to content
This repository has been archived by the owner on Apr 6, 2023. It is now read-only.

feat(nuxt): improve error dx for users #4539

Merged
merged 54 commits into from
Jul 21, 2022
Merged
Show file tree
Hide file tree
Changes from 11 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
14 changes: 13 additions & 1 deletion docs/content/2.guide/2.features/7.error-handling.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,10 +82,22 @@ This function will return the global Nuxt error that is being handled.

### `throwError`

* `function throwError (err: string | Error): Error`
* `function throwError (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 (as above) which you can clear with `clearError`.

### Example

```vue [pages/movies/[slug].vue]
<script setup>
const route = useRoute()
const { data, error } = await useFetch(`/api/movies/${route.params.slug}`)
if (error.value) {
pi0 marked this conversation as resolved.
Show resolved Hide resolved
return throwError({ statusCode: 404, statusMessage: 'Page Not Found' })
pi0 marked this conversation as resolved.
Show resolved Hide resolved
}
</script>
```

::ReadMore{link="/api/utils/throw-error"}
::

Expand Down
20 changes: 19 additions & 1 deletion docs/content/3.api/3.utils/throw-error.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,11 @@ Within your pages, components and plugins you can use `throwError` to throw an e

**Parameters:**

- `error`: `string | Error`
- `error`: `string | Error | Partial<H3Error>`
danielroe marked this conversation as resolved.
Show resolved Hide resolved

```js
throwError("😱 Oh no, an error has been thrown.")
throwError({ statusCode: 404, statusMessage: "Page Not Found" })
```

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.
Expand All @@ -18,3 +19,20 @@ The thrown error is set in the state using [`useError()`](/api/composables/use-e

::ReadMore{link="/guide/features/error-handling"}
::

## Throwing errors in API routes

You can use `createError` from [`h3`](https://github.com/unjs/h3) package to trigger error handling in server API routes.

**Example:**

```js
import { createError } from 'h3'
danielroe marked this conversation as resolved.
Show resolved Hide resolved

export default eventHandler(() => {
throw createError({
statusCode: 404,
statusMessage: 'Page Not Found'
})
}
```
2 changes: 1 addition & 1 deletion packages/nuxt/src/app/components/nuxt-error-page.vue
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ const stacktrace = (error.stack || '')
const statusCode = String(error.statusCode || 500)
const is404 = statusCode === '404'

const statusMessage = error.statusMessage ?? is404 ? 'Page Not Found' : 'Internal Server Error'
const statusMessage = error.statusMessage ?? (is404 ? 'Page Not Found' : 'Internal Server Error')
const description = error.message || error.toString()
const stack = process.dev && !is404 ? error.description || `<pre>${stacktrace}</pre>` : undefined

Expand Down
9 changes: 7 additions & 2 deletions packages/nuxt/src/app/composables/error.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,19 @@
import { 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) => {
export const throwError = (_err: string | Error | Partial<H3Error>) => {
const nuxtApp = useNuxtApp()
const error = useError()
const err = typeof _err === 'string' ? new Error(_err) : _err
const err = typeof _err === 'string'
? new Error(_err)
danielroe marked this conversation as resolved.
Show resolved Hide resolved
: _err && typeof _err === 'object' && !(_err instanceof Error)
? createError(_err)
pi0 marked this conversation as resolved.
Show resolved Hide resolved
: _err
nuxtApp.callHook('app:error', err)
if (process.server) {
nuxtApp.ssrContext.error = nuxtApp.ssrContext.error || err
Expand Down
9 changes: 4 additions & 5 deletions packages/nuxt/src/app/plugins/router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -106,11 +106,6 @@ export default defineNuxtPlugin<{ route: Route, router: Router }>((nuxtApp) => {
// Resolve route
const to = getRouteFromPath(url)

if (process.client && !nuxtApp.isHydrating) {
// Clear any existing errors
await callWithNuxt(nuxtApp, clearError)
}

// Run beforeEach hooks
for (const middleware of hooks['navigate:before']) {
const result = await middleware(to, route)
Expand All @@ -127,6 +122,10 @@ export default defineNuxtPlugin<{ route: Route, router: Router }>((nuxtApp) => {
Object.assign(route, to)
if (process.client) {
window.history[replace ? 'replaceState' : 'pushState']({}, '', url)
if (!nuxtApp.isHydrating) {
// Clear any existing errors
await callWithNuxt(nuxtApp, clearError)
}
}
// Run afterEach hooks
for (const middleware of hooks['navigate:after']) {
Expand Down
9 changes: 4 additions & 5 deletions packages/nuxt/src/pages/runtime/router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -125,11 +125,6 @@ export default defineNuxtPlugin((nuxtApp) => {
}
}

if (process.client && !nuxtApp.isHydrating) {
// Clear any existing errors
await callWithNuxt(nuxtApp, clearError)
}

for (const entry of middlewareEntries) {
const middleware = typeof entry === 'string' ? nuxtApp._middleware.named[entry] || await namedMiddleware[entry]?.().then(r => r.default || r) : entry

Expand All @@ -152,6 +147,10 @@ export default defineNuxtPlugin((nuxtApp) => {

router.afterEach(() => {
delete nuxtApp._processingMiddleware
if (process.client && !nuxtApp.isHydrating) {
// Clear any existing errors
callWithNuxt(nuxtApp, clearError)
}
})

nuxtApp.hook('app:created', async () => {
Expand Down