This article explores errors you may face in building your Nuxt 3 application, and how you can handle these errors to make your application rock solid.
Get notified when we release new tutorials, lessons, and other expert Nuxt content.
Throwing and handling errors is important for making any app rock solid in production.
Nuxt 3 makes this easy for us, and in this article I’ll show you exactly how.
We’ll cover:
NuxtErrorBoundary
to isolate client-side errors from infecting the rest of our applicationIt may not be the most interesting topic, but error handling is hugely important for building well-engineered software.
So let’s get to it!
To create an error, we’ll throw a Nuxt error that’s returned by the createError
method:
throw createError({
statusCode: 500,
statusMessage: 'Something bad happened on the server',
});
The createError
method is isomorphic, meaning it can be called on the server or on the client.
So you’ll use it inside of the Vue part of your app — components, composables, and Pinia stores:
import { StorageSerializers } from '@vueuse/core';
export default async <T>(url: string) => {
// Use sessionStorage to cache the lesson data
const cached = useSessionStorage<T>(url, null, {
// By passing null as default it can't automatically
// determine which serializer to use
serializer: StorageSerializers.object,
});
if (!cached.value) {
const { data, error } = await useFetch<T>(url);
// 👇 Throw an error if the API isn't working
if (error.value) {
throw createError({
...error.value,
statusMessage: `Could not fetch data from ${url}`,
});
}
// Update the cache
cached.value = data.value as T;
} else {
console.log(`Getting value from cache for ${url}`);
}
return cached;
};
(Learn more about creating this useFetchWithCache
composable in this tutorial I wrote.)
As well as inside your Nitro event handlers in your API:
import { PrismaClient } from '@prisma/client';
const prisma = new PrismaClient();
export default defineEventHandler(async (event) => {
const { chapterSlug, lessonSlug } = event.context.params;
const lesson = await prisma.lesson.findFirst({
where: {
slug: lessonSlug,
chapter: {
slug: chapterSlug,
},
},
});
// 👇 Throw an error if we can't find the lesson
if (!lesson) {
throw createError({
statusCode: 404,
statusMessage: 'Lesson not found',
});
}
return lesson;
});
(I wrote a whole series on using Prisma with Nuxt and Supabase if you want to learn more about that.)
But throwing errors is only half of the equation.
Once errors are thrown, we know something broke. The next step is to actually deal with the problem — which also happens to be one of the hardest parts of being an adult…
To deal with errors we’ll employ three basic steps:
useError
composableclearError
to move on once we’ve addressed the issueThis is how we’d use them together:
const error = useError();
const handleError = () => {
clearError({
redirect: '/dashboard',
});
};
We can display info about the error, including the message
and statusCode
, and then use clearError
to reset the error.
Here, we’ve only addressed 1 and 3, but how do we handle the actual error?
Well, that entirely depends on your application, and what was happening at the moment. If you were trying to log a user in, perhaps you need to show them a “Forgot Password” dialog. If they were trying to export a PDF report from a dashboard, maybe the right way to handle the error is to just retry the export.
However, in many cases, a good default is to redirect back to the main page. This is why clearError
has the redirect
option built-in:
clearError({ redirect: '/dashboard' });
Now, let’s take a moment to better understand how errors work in Nuxt 3.
We have two types of errors:
It’s important to understand this distinction because these errors happen under different circumstances and need to be handled differently.
Global errors can happen any time the server is executing code. Mainly, this is during an API call, during a server-side render, or any of the code that glues these two together.
Client-side errors mainly happen while interacting within an app, but they can also happen on route changes because of how Nuxt’s Universal Rendering works.
We’ve already seen how these global errors work, so let’s take a look at how client-side errors are different.
The main difference with client-side errors is that they only exist in the user's browser.
The other difference is that they can be localized to a specific part of your application. Instead of crashing your entire app if something breaks, we can make sure that only a small part of your app breaks, leaving the rest fully functional.
We can do this using the NuxtErrorBoundary
component that is built-in to Nuxt:
<NuxtErrorBoundary>
<VideoPlayer />
<template #error="{ error }">
<div>
<p>Oops, it looks like the video player broke :/</p>
<p>{{ error.message }}</p>
</div>
</template>
</NuxtErrorBoundary>
Any errors that happen in the component’s default slot are captured, preventing them from affecting the rest of the app. In this case, we’re wrapping the VideoPlayer
component in a “protective shield” using NuxtErrorBoundary
.
We can then use the error
named slot to show a very specific error message that is tailored to this specific use-case. This is why I love NuxtErrorBoundary
so much because it lets us create incredibly specific messages — and actions to recover from the error — instead of a generic, “whoops, something broke!”.
I wrote an entire deep dive on client-side error handling if you want to learn more.
We’ve seen how we can manage and work with errors in Nuxt 3, all the way from the backend to the frontend.
Using throw createError()
we can trigger errors anywhere in our app — in event handlers, SSR, or client-side code.
Then, using the useError
and clearError
methods, we can gather info about the error and then handle it properly.
Even better, we can use NuxtErrorBoundary
to handle client-side only errors without ever hitting the server. We can also provide extremely specific error messages and actions to provide an even better UX for our users.
If you want to learn more about error handling, check out Mastering Nuxt 3, where we show how to apply all of these techniques to a full-stack app. You can also check out the docs for more info.