Next.js Lesson 24 – Error Handling | Dataplexa
LESSON 24

Error Handling

Build comprehensive error handling for NewsWave using custom error pages, error boundaries, and global error management strategies.

Errors happen. Users break things. APIs fail. Networks timeout. Your NewsWave site needs to handle these gracefully instead of showing cryptic white screens or browser error messages. Next.js provides several layers of error handling. Custom error pages catch route-level failures. Error boundaries trap React component crashes. Global error handlers manage API failures. Think of it like a safety net system — multiple layers catch different types of problems. Unlike plain React where you manually configure error boundaries everywhere, Next.js has built-in conventions. The error.js file automatically wraps page components. The not-found.js handles 404s. The global-error.js catches everything else.

Custom Error Pages

Error pages replace the default browser error screen with branded experiences. When a NewsWave article fails to load, users should see a helpful message, not a technical stack trace. Start with the 404 page. This handles "page not found" scenarios — like visiting /articles/fake-slug when that article doesn't exist. The not-found.js file creates custom 404 pages. Next comes the general error page. This catches runtime errors in your page components — like JavaScript exceptions or failed data fetching. The error.js file creates these error boundaries.
// app/not-found.js - Custom 404 page for NewsWave
export default function NotFound() {
  return (
    <div className="error-container">
      <h1>Article Not Found</h1>
      <p>The article you're looking for doesn't exist.</p>
      <a href="/">Back to NewsWave Home</a>
    </div>
  );
}
localhost:3000/articles/missing — NewsWave
What just happened?
Next.js automatically shows this component when routes don't exist. The branded design keeps users engaged instead of bouncing. Try this: Visit any non-existent URL to see your custom 404 page.
Now create the main error boundary. This catches JavaScript errors, failed API calls, and other runtime problems. Unlike 404s which are predictable, these errors are unexpected crashes.
// app/error.js - Main error boundary for NewsWave
'use client'; // Error boundaries must be client components

export default function Error({ error, reset }) {
  return (
    <div className="error-container">
      <h1>Something went wrong</h1>
      <p>NewsWave encountered an unexpected error.</p>
      <button onClick={reset}>Try Again</button>
    </div>
  );
}
localhost:3000 — NewsWave Error
What just happened?
The error boundary catches crashes and shows recovery options. The reset function re-renders the failed component. Try this: The 'use client' directive makes this a client component since error boundaries need React hooks.

Global Error Handling

Some errors happen outside individual pages — like layout crashes or root-level failures. The global-error.js file catches these application-wide problems. Global error handlers are the final safety net. When everything else breaks — your layout, your root component, even your main error boundary — this component takes over. It completely replaces your app's HTML structure. Think of it like an emergency backup generator. Most of the time it sits idle. But when the main power grid fails, it keeps essential systems running. Your global error handler keeps NewsWave functional even when core components crash.
// app/global-error.js - Last resort error handler
'use client';

export default function GlobalError({ error, reset }) {
  return (
    <html> {/* Must include html and body tags */}
      <body>
        <div className="global-error">
          <h1>NewsWave is temporarily unavailable</h1>
          <p>We're experiencing technical difficulties.</p>
          <button onClick={reset}>Reload Page</button>
        </div>
      </body>
    </html>
  );
}
Terminal
$ npm run dev
Starting development server...
✓ Global error handler registered
Ready on http://localhost:3000
What just happened?
Global error handlers must include complete HTML structure since they replace everything. Next.js automatically activates this when other error boundaries fail. Try this: This only triggers for catastrophic errors like layout crashes.

API Error Handling

API routes need their own error handling strategy. When the NewsWave newsletter signup fails or article fetching breaks, you want controlled responses instead of server crashes. API error handling happens in two places. First, inside your API route handlers — catching database failures, validation errors, and external API problems. Second, in your client code — handling network timeouts, 500 errors, and malformed responses. The pattern involves try-catch blocks around risky operations, proper HTTP status codes for different error types, and consistent error response formats. Your frontend code can then handle these predictable error structures.
// app/api/articles/route.js - API error handling
export async function GET() {
  try {
    // Simulate database fetch
    const articles = await fetchArticles();
    return Response.json({ articles });
  } catch (error) {
    return Response.json(
      { error: 'Failed to load articles' },
      { status: 500 }
    );
  }
}
Terminal
$ curl http://localhost:3000/api/articles
{"error":"Failed to load articles"}
HTTP 500 Internal Server Error
Now handle these API errors in your components. The fetch might fail due to network issues, server errors, or malformed responses. Always assume something can go wrong and prepare fallback content.
// components/ArticleList.js - Client-side API error handling
'use client';
import { useState, useEffect } from 'react';

export default function ArticleList() {
  const [articles, setArticles] = useState([]);
  const [error, setError] = useState(null);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    fetchArticles();
  }, []);

  const fetchArticles = async () => {
    try {
      const response = await fetch('/api/articles');
      
      if (!response.ok) {
        throw new Error(`HTTP ${response.status}`);
      }
      
      const data = await response.json();
      setArticles(data.articles);
    } catch (err) {
      setError(err.message);
    } finally {
      setLoading(false);
    }
  };

  if (loading) return <div>Loading articles...</div>;
  if (error) return <div>Error: {error}</div>;
  
  return (
    <div>
      {articles.map(article => (
        <div key={article.id}>{article.title}</div>
      ))}
    </div>
  );
}
localhost:3000 — NewsWave Articles
What just happened?
The component handles three states — loading, error, and success. The try-catch blocks prevent crashes and show user-friendly messages. Try this: The demo randomly shows success or error states to simulate real API behavior.

Error Boundaries for Components

Sometimes you want granular error handling — catching errors in specific components without crashing the entire page. React error boundaries wrap individual components and isolate their failures. Error boundaries work like circuit breakers in electrical systems. When one section overloads, the breaker trips and cuts power to just that area. The rest of your house keeps working normally. Component-level error boundaries protect your page the same way. For NewsWave, you might wrap the comment section in an error boundary. If comments fail to load, the article content still displays perfectly. Users can read the news even when secondary features break.
// components/ErrorBoundary.js - Reusable error boundary
'use client';
import { ErrorBoundary } from 'react-error-boundary';

function ErrorFallback({ error, resetErrorBoundary }) {
  return (
    <div className="error-fallback">
      <h3>Something went wrong</h3>
      <p>{error.message}</p>
      <button onClick={resetErrorBoundary}>Try again</button>
    </div>
  );
}

export default function SafeComponent({ children }) {
  return (
    <ErrorBoundary
      FallbackComponent={ErrorFallback}
      onReset={() => window.location.reload()}
    >
      {children}
    </ErrorBoundary>
  );
}
Terminal
$ npm install react-error-boundary
Installing error boundary library...
✓ react-error-boundary@4.0.11 added
Now wrap risky components with your error boundary. The NewsWave comment section is perfect — it loads external data, handles user input, and could fail in multiple ways.
// app/articles/[slug]/page.js - Using error boundary
import SafeComponent from '@/components/ErrorBoundary';
import CommentSection from '@/components/CommentSection';

export default function ArticlePage() {
  return (
    <main>
      <article>
        <h1>Breaking News Article</h1>
        <p>Article content loads reliably...</p>
      </article>
      
      <SafeComponent>
        <CommentSection /> {/* This might crash */}
      </SafeComponent>
    </main>
  );
}
localhost:3000/articles/breaking-news — NewsWave
What just happened?
The error boundary isolates the broken comment section. Main article content remains accessible while secondary features show recovery options. Try this: This pattern keeps your core content readable even when optional features crash.

Error Logging and Monitoring

Good error handling doesn't just fix problems — it reports them so you can prevent future issues. Error logging captures crash details, user context, and stack traces for debugging. Production error monitoring is like a security camera system. When something breaks, you want to know what happened, when it occurred, and how to reproduce the problem. Services like Sentry, LogRocket, or DataDog capture this information automatically. Basic error logging starts with console messages and server logs. Advanced monitoring includes user session replays, performance metrics, and automated alerting when error rates spike.
// lib/errorLogger.js - Custom error logging
export function logError(error, context = {}) {
  // Log to console in development
  if (process.env.NODE_ENV === 'development') {
    console.error('Error:', error);
    console.error('Context:', context);
    return;
  }
  
  // Send to monitoring service in production
  const errorData = {
    message: error.message,
    stack: error.stack,
    timestamp: new Date().toISOString(),
    userAgent: navigator.userAgent,
    url: window.location.href,
    ...context
  };
  
  // Replace with your monitoring service
  fetch('/api/errors', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(errorData)
  });
}
Terminal
$ npm install @sentry/nextjs
Installing Sentry monitoring...
✓ @sentry/nextjs@7.75.0 added
Run npx @sentry/wizard -i nextjs
Integrate error logging into your error boundaries and API routes. Every caught error should be logged with relevant context — user ID, page URL, browser version, and reproduction steps.
// app/error.js - Error boundary with logging
'use client';
import { logError } from '@/lib/errorLogger';
import { useEffect } from 'react';

export default function Error({ error, reset }) {
  useEffect(() => {
    // Log error with context
    logError(error, {
      component: 'PageErrorBoundary',
      userAction: 'pageLoad',
      severity: 'high'
    });
  }, [error]);

  return (
    <div className="error-container">
      <h1>Something went wrong</h1>
      <p>NewsWave encountered an error. Our team has been notified.</p>
      <button onClick={reset}>Try Again</button>
    </div>
  );
}
Terminal
Error logged to monitoring service
Timestamp: 2024-01-15T14:30:22.123Z
Component: PageErrorBoundary
Severity: high
What just happened?
Error logging captures crash details automatically. Production monitoring helps you fix problems before users complain. Try this: The useEffect hook logs errors once when the boundary mounts.
Development Errors
Show detailed stack traces, hot reload on fixes, console logging for debugging.
Production Errors
Hide technical details, send to monitoring services, show user-friendly messages.
Client Errors
Network failures, component crashes, validation errors, API timeouts.
Server Errors
Database failures, API route crashes, build errors, middleware problems.

Error Recovery Patterns

The best error handling doesn't just show error messages — it helps users recover and continue their tasks. Recovery patterns provide alternative paths when primary features fail. Think about ATM machines. When the card reader breaks, you can still withdraw cash by typing your account number. When the receipt printer fails, you get digital receipts. Good error recovery keeps core functionality working even when individual components break. For NewsWave, implement graceful degradation. If article images fail to load, show placeholder graphics. If the search API times out, fall back to client-side filtering. If comments won't submit, save drafts locally for retry later.
// components/ArticleImage.js - Image with fallback
'use client';
import { useState } from 'react';

export default function ArticleImage({ src, alt, title }) {
  const [imageError, setImageError] = useState(false);
  const [loading, setLoading] = useState(true);

  if (imageError) {
    return (
      <div className="image-fallback">
        <div className="placeholder">📰</div>
        <p>{title}</p>
      </div>
    );
  }

  return (
    <div className="article-image">
      {loading && <div className="image-loading">Loading...</div>}
      <img
        src={src}
        alt={alt}
        onLoad={() => setLoading(false)}
        onError={() => {
          setLoading(false);
          setImageError(true);
        }}
        style={{ display: loading ? 'none' : 'block' }}
      />
    </div>
  );
}
localhost:3000 — NewsWave Article
What just happened?
The component gracefully degrades from loading to image to fallback placeholder. Users get meaningful content even when images fail to load. Try this: The onError handler automatically switches to fallback content.
Error handling in Next.js protects your users from crashes and gives you insight into production problems. Custom error pages maintain your brand during failures. Error boundaries isolate component crashes. API error handling prevents server crashes. And error logging helps you fix problems quickly. The key is layered protection — multiple safety nets catch different types of failures. Your NewsWave site stays functional even when individual pieces break. Users get helpful error messages instead of blank screens. And you get the data needed to prevent future problems.

Quiz

1. The NewsWave team notices that when individual page components crash, users see blank white screens. What Next.js file should they create to handle these errors gracefully?


2. NewsWave wants to ensure that when the comment section fails to load, the main article content remains readable. What's the best approach?


3. When NewsWave fetches articles from /api/articles, the API might return HTTP 500 errors or network timeouts. What's the proper way to handle these in the client component?


Up Next: Next.js Best Practices

Master production-ready patterns, performance optimization, security hardening, and professional development workflows for Next.js applications.