DEV Community

Cover image for Complete Guide to Error Handling in Next.js: From Basics to Best Practices
sudip khatiwada
sudip khatiwada

Posted on

Complete Guide to Error Handling in Next.js: From Basics to Best Practices

Meta Description: Learn Next.js error handling with practical examples. Master global error handling, nested route errors, client-side exceptions, and recovery techniques in this beginner-friendly guide.


Picture this: you're browsing an online store, excited to buy that perfect gift, when suddenly – CRASH! A white screen appears with cryptic error messages. Frustrating, right? As developers, we never want our users to experience this nightmare.

Error handling in Next.js is like having a safety net for tightrope walkers – it catches problems before they become disasters. Whether you're building your first Next.js app or looking to improve your error handling skills, this guide will walk you through everything you need to know.

Why Error Handling Matters in Next.js

Think of error handling as your app's immune system. Just like our bodies fight off infections to keep us healthy, proper error handling protects your application from crashes and provides users with meaningful feedback when something goes wrong.

In Next.js applications, errors can occur at different levels:

  • Server-side rendering failures
  • API route errors
  • Client-side JavaScript exceptions
  • Component rendering issues

Without proper Next.js error handling, these issues can break your entire application, leaving users staring at blank screens or cryptic error messages.

Global Error Handling with Root-Level error.js

Global error handling in Next.js is like having a master safety switch for your entire house. When something goes wrong anywhere in your app, this catches it.

Folder Structure

app/
├── layout.js
├── page.js
├── error.js          ← Global error handler
└── loading.js
Enter fullscreen mode Exit fullscreen mode

Example: Creating a Global Error Handler

// app/error.js
'use client'

export default function GlobalError({ error, reset }) {
  return (
    <div className="error-container">
      <h2>Oops! Something went wrong</h2>
      <p>Don't worry, our team has been notified.</p>
      <details>
        <summary>Error Details (for developers)</summary>
        <pre>{error.message}</pre>
      </details>
      <button 
        onClick={reset}
        className="retry-button"
      >
        Try Again
      </button>
    </div>
  )
}
Enter fullscreen mode Exit fullscreen mode

This global error.js file acts as your application's emergency response team. Whenever an unhandled error occurs anywhere in your app, this component springs into action, providing users with a friendly message instead of a scary crash screen.

Error Handling in Nested Routes

Sometimes you want different error handling for different sections of your app – like having specialized emergency procedures for different floors of a building. This is where route-level error handling shines.

Folder Structure for Nested Route Errors

app/
├── error.js                    ← Global fallback
├── dashboard/
│   ├── error.js               ← Dashboard-specific errors
│   ├── page.js
│   └── analytics/
│       ├── error.js           ← Analytics-specific errors
│       └── page.js
└── blog/
    ├── error.js               ← Blog-specific errors
    └── page.js
Enter fullscreen mode Exit fullscreen mode

Example: Dashboard Error Handler

// app/dashboard/error.js
'use client'

export default function DashboardError({ error, reset }) {
  // Log error to your monitoring service
  console.error('Dashboard Error:', error)

  return (
    <div className="dashboard-error">
      <h2>Dashboard Temporarily Unavailable</h2>
      <p>We're experiencing some technical difficulties with your dashboard.</p>
      <div className="error-actions">
        <button onClick={reset}>
          Refresh Dashboard
        </button>
        <a href="/support" className="support-link">
          Contact Support
        </a>
      </div>
    </div>
  )
}
Enter fullscreen mode Exit fullscreen mode

The beauty of nested error handling is that it provides context-specific solutions. A dashboard error might offer different recovery options than a blog post error.

Recovering from Errors with the reset() Function

The reset() function in Next.js error handling is like having a "reset" button on your router when the internet stops working. It attempts to re-render the component that caused the error.

How reset() Works

// app/products/error.js
'use client'

import { useEffect } from 'react'

export default function ProductsError({ error, reset }) {
  useEffect(() => {
    // Log error to monitoring service
    console.error('Products page error:', error)
  }, [error])

  return (
    <div className="products-error">
      <h2>Unable to Load Products</h2>
      <p>Something went wrong while loading our products.</p>

      {/* Smart retry with different messaging */}
      <div className="retry-options">
        <button 
          onClick={reset}
          className="primary-retry"
        >
          Try Loading Again
        </button>

        <button 
          onClick={() => {
            // Clear any cached data first
            if (typeof window !== 'undefined') {
              localStorage.removeItem('products-cache')
            }
            reset()
          }}
          className="secondary-retry"
        >
          Clear Cache & Retry
        </button>
      </div>
    </div>
  )
}
Enter fullscreen mode Exit fullscreen mode

The reset() function is particularly useful for temporary network issues or transient errors that might resolve themselves on retry.

Handling Client-Side Exceptions with React Error Boundaries

While Next.js error.js files handle server-side and routing errors, sometimes you need to catch client-side JavaScript errors within specific components. This is where React Error Boundaries come in – think of them as localized safety nets.

Creating a Custom Error Boundary

// components/ErrorBoundary.js
'use client'

import React from 'react'

class ErrorBoundary extends React.Component {
  constructor(props) {
    super(props)
    this.state = { hasError: false, error: null }
  }

  static getDerivedStateFromError(error) {
    // Update state so the next render shows fallback UI
    return { hasError: true, error }
  }

  componentDidCatch(error, errorInfo) {
    // Log error to your error reporting service
    console.error('ErrorBoundary caught an error:', error, errorInfo)
  }

  render() {
    if (this.state.hasError) {
      return (
        <div className="error-boundary">
          <h3>Something went wrong in this section</h3>
          <p>This component encountered an error, but the rest of the page should work fine.</p>
          <button 
            onClick={() => this.setState({ hasError: false, error: null })}
          >
            Try Again
          </button>
        </div>
      )
    }

    return this.props.children
  }
}

export default ErrorBoundary
Enter fullscreen mode Exit fullscreen mode

Using the Error Boundary

// app/dashboard/page.js
import ErrorBoundary from '@/components/ErrorBoundary'
import UserProfile from '@/components/UserProfile'
import ActivityFeed from '@/components/ActivityFeed'

export default function Dashboard() {
  return (
    <div className="dashboard">
      <h1>Dashboard</h1>

      <ErrorBoundary>
        <UserProfile />
      </ErrorBoundary>

      <ErrorBoundary>
        <ActivityFeed />
      </ErrorBoundary>
    </div>
  )
}
Enter fullscreen mode Exit fullscreen mode

This approach ensures that if one component crashes, it doesn't take down the entire page – like having firewall doors in a building that prevent fires from spreading.

Advanced Error Handling Patterns

Combining Error Boundaries with Suspense

// components/SafeAsyncComponent.js
import { Suspense } from 'react'
import ErrorBoundary from './ErrorBoundary'

export default function SafeAsyncComponent({ children }) {
  return (
    <ErrorBoundary>
      <Suspense fallback={<div>Loading...</div>}>
        {children}
      </Suspense>
    </ErrorBoundary>
  )
}
Enter fullscreen mode Exit fullscreen mode

Error Logging and Monitoring

// utils/errorLogger.js
export function logError(error, errorInfo = {}) {
  if (process.env.NODE_ENV === 'production') {
    // Send to your error monitoring service
    // Example: Sentry, LogRocket, etc.
    console.error('Production Error:', error, errorInfo)
  } else {
    console.error('Development Error:', error, errorInfo)
  }
}
Enter fullscreen mode Exit fullscreen mode

Summary and Best Practices

Error handling in Next.js is like building a comprehensive safety system for your application. Here's what we covered:

  • Global error handling catches application-wide issues
  • Nested route errors provide context-specific error experiences
  • The reset() function offers users a way to recover from errors
  • Error boundaries protect individual components from crashing the entire page

Quick Tips for Better Next.js Error Handling:

  1. Always provide user-friendly error messages – avoid technical jargon
  2. Include recovery options – give users something to do, not just something to read
  3. Log errors appropriately – capture details for debugging without exposing sensitive information
  4. Test your error handlers – simulate errors during development to ensure they work
  5. Use progressive error handling – start with specific handlers and fall back to general ones
  6. Consider offline states – handle network connectivity issues gracefully

Remember, good error handling isn't just about preventing crashes – it's about creating a smooth, professional user experience even when things go wrong. Your users will thank you for it!


Want to learn more about Next.js development? Check out our other guides on Next.js performance optimization and deployment strategies.

Top comments (0)