Nuxt Error Handling — Custom Error Pages, Error Boundaries, and API Error Handling
In this tutorial, you will learn about Nuxt Error Handling. We cover key concepts, practical examples, and best practices to help you master this topic.
Learn Nuxt error handling — create custom 404 and 500 error pages, implement error boundaries, handle API errors gracefully, and display user-friendly error messages.
In this lesson, you'll understand how to handle errors in Nuxt at every level — pages, API routes, components, and server-side — with proper user feedback and logging.
What You'll Learn
How to create custom error pages, use createError and showError for programmatic error handling, implement error boundaries with NuxtErrorBoundary, handle API errors in server routes, and log errors for debugging.
Why It Matters
Users encounter errors — broken links, network failures, server crashes — and how your application responds determines whether they stay or leave. Good error handling turns frustration into a guided recovery experience.
Real-World Use
An e-commerce platform with 10,000+ products uses custom Nuxt error pages and error boundaries so that when a product API fails, only the product card shows an error state instead of crashing the entire page.
flowchart TD
A[Error Occurs] --> B{Error Type}
B -->|404 Not Found| C[Custom 404 Page]
B -->|500 Server Error| D[Custom 500 Page]
B -->|Component Error| E[NuxtErrorBoundary]
B -->|API Error| F[Error Response]
C --> G[User-friendly Message]
D --> G
E --> H[Fallback UI]
F --> I[Structured Error JSON]
style A fill:#00dc82,color:#fff
Custom Error Pages
Create error.vue in the project root to handle all errors:
<template>
<div class="error-page">
<div class="error-content">
<h1>{{ error.statusCode }}</h1>
<p>{{ error.message || 'An unexpected error occurred' }}</p>
<p class="error-description">
<template v-if="error.statusCode === 404">
The page you're looking for doesn't exist or has been moved.
</template>
<template v-else-if="error.statusCode === 500">
Something went wrong on our end. Please try again later.
</template>
<template v-else>
We encountered an issue. Please go back or try again.
</template>
</p>
<div class="error-actions">
<NuxtLink to="/" class="btn btn-primary">
Go Home
</NuxtLink>
<button @click="handleError" class="btn btn-secondary">
Try Again
</button>
</div>
</div>
</div>
</template>
<script setup>
const props = defineProps({
error: Object
});
const handleError = () => clearError({ redirect: '/' });
</script>
Expected output: A styled error page that shows the status code, a human-readable message, and action buttons based on the error type.
Programmatic Error Throwing
Use createError to throw errors in pages and composables:
<script setup>
const route = useRoute();
const { data: product } = await useAsyncData('product', () => {
return $fetch(`/api/products/${route.params.id}`);
});
// Handle missing data
if (!product.value) {
throw createError({
statusCode: 404,
statusMessage: 'Product Not Found',
message: `No product found with ID "${route.params.id}"`,
fatal: true // Show error page
});
}
// Handle validation errors
if (route.params.id.length < 3) {
throw createError({
statusCode: 400,
statusMessage: 'Bad Request',
message: 'Product ID must be at least 3 characters'
});
}
</script>
<template>
<div class="product-page" v-if="product">
<h1>{{ product.name }}</h1>
<p>{{ product.description }}</p>
</div>
</template>
Expected output: Missing products show a 404 error page. Invalid IDs show a 400 error. Both display user-friendly messages without crashing the application.
NuxtErrorBoundary
Wrap components that might fail to prevent full-page crashes:
<template>
<div class="dashboard">
<h1>Dashboard</h1>
<NuxtErrorBoundary @error="logError">
<template #error="{ error }">
<div class="error-fallback">
<p>Failed to load user profile.</p>
<button @click="resetError">Retry</button>
<details>
<summary>Technical details</summary>
<pre>{{ error.message }}</pre>
</details>
</div>
</template>
<UserProfile :user-id="userId" />
</NuxtErrorBoundary>
<NuxtErrorBoundary>
<template #error>
<p class="fallback">Analytics widget unavailable.</p>
</template>
<AnalyticsWidget />
</NuxtErrorBoundary>
</div>
</template>
<script setup>
function logError(error) {
console.error('Boundary caught:', error);
// Send to error tracking service
}
</script>
Expected output: If UserProfile or AnalyticsWidget throws an error, only that component shows the fallback — the rest of the dashboard continues working normally.
API Error Handling
Server-side API routes should return structured errors:
// server/api/products/[id].ts
export default defineEventHandler(async (event) => {
const id = getRouterParam(event, 'id');
if (!id || id.length < 3) {
throw createError({
statusCode: 400,
statusMessage: 'Bad Request',
data: {
field: 'id',
reason: 'Product ID must be at least 3 characters',
received: id
}
});
}
const product = await findProductById(id);
if (!product) {
throw createError({
statusCode: 404,
statusMessage: 'Not Found',
data: {
reason: `No product exists with ID "${id}"`,
suggestion: 'Check the product ID and try again'
}
});
}
return product;
});
Handle API errors on the client:
<script setup>
const route = useRoute();
const error = ref(null);
const { data: product } = await useAsyncData('product', async () => {
try {
return await $fetch(`/api/products/${route.params.id}`);
} catch (e) {
error.value = {
status: e.response?.status || 500,
message: e.response?._data?.statusMessage || 'Failed to load product',
details: e.response?._data?.data || {}
};
return null;
}
});
</script>
<template>
<div v-if="error" class="error-state">
<h2>Error {{ error.status }}</h2>
<p>{{ error.message }}</p>
<p v-if="error.details.suggestion">{{ error.details.suggestion }}</p>
<NuxtLink to="/products">Back to Products</NuxtLink>
</div>
<div v-else-if="product" class="product">
<h1>{{ product.name }}</h1>
</div>
</template>
Expected output: API errors show contextual messages and suggestions instead of raw error text, helping users understand and resolve the issue.
Global Error Logging
Set up a plugin for centralized error tracking:
// plugins/error-logger.client.ts
export default defineNuxtPlugin({
name: 'error-logger',
setup(app) {
// Vue error handler
app.vueApp.config.errorHandler = (err, instance, info) => {
console.error('[Vue Error]', err);
console.error('[Component]', instance?.$options?.name);
console.error('[Info]', info);
// Send to error tracking service
if (process.env.NODE_ENV === 'production') {
fetch('/api/log-error', {
method: 'POST',
body: JSON.stringify({
message: err.message,
stack: err.stack,
timestamp: new Date().toISOString(),
url: window.location.href
})
}).catch(() => {});
}
};
// Global promise rejection handler
window.addEventListener('unhandledrejection', (event) => {
console.error('[Unhandled Promise Rejection]', event.reason);
});
}
});
Expected output: All uncaught Vue errors and promise rejections are logged to the console in development and sent to an error tracking endpoint in production.
Common Mistakes
Not handling errors in useAsyncData and useFetch: These composables set an
errorref but don't automatically prevent rendering. Always checkerror.valuebefore rendering data-dependent content.Showing raw error messages to users: Technical error messages (SQL errors, stack traces) confuse and frustrate users. Always map to user-friendly messages before displaying.
Not providing fallback UI in NuxtErrorBoundary: Without the
#errortemplate, the boundary silently swallows errors without feedback. Always provide a fallback that tells the user something went wrong.Forgetting to clear errors after recovery:
clearError()resets the error state. Without it, users stay on the error page even after the error condition is resolved.Not distinguishing between fatal and non-fatal errors: Use
fatal: truefor unrecoverable errors that should show the error page, and local error handling for recoverable component failures.
Practice Questions
What is the difference between
createErrorandshowError? Answer:createErrorcreates an error object you can throw.showErrorimmediately displays the error page. UsecreateErrorwiththrowin pages andshowErrorin middleware or plugins.How does NuxtErrorBoundary prevent full-page crashes? Answer: It catches errors thrown by its child components and renders the
#errorfallback instead. The rest of the page outside the boundary continues working.What information should a structured API error include? Answer: Status code, human-readable message, field-level validation details, suggestions for resolution, and optional debug information for developers.
Why should you use
clearErrorafter handling an error? Answer: WithoutclearError, the error state persists and the error page continues showing. Call it to reset the error state and navigate back to normal content.
Challenge
Build a resilient data dashboard with: three independently loaded widgets wrapped in NuxtErrorBoundary, a custom 404 page with search suggestions, a custom 500 page with status check links, API routes that return structured errors with suggestions, a global error logger that captures all errors, and a retry mechanism for failed API calls using the error boundary's reset function.
Mini Project
Create an error-handled application with: custom 404 and 500 pages with branded styling, error boundaries around each major section (header, content, sidebar, footer), API routes with structured error responses including field validation details, a centralized error logging service, automatic retry for transient failures (network timeout, 503), and a user-facing error toast component for non-critical errors.
FAQ
What's Next
Learn about Nuxt Testing to write unit tests, component tests, and end-to-end tests for your Nuxt application with Vitest and @nuxt/test-utils.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro