惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

有赞技术团队
有赞技术团队
美团技术团队
博客园 - 司徒正美
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
阮一峰的网络日志
阮一峰的网络日志
S
SegmentFault 最新的问题
博客园_首页
雷峰网
雷峰网
V
V2EX
The Cloudflare Blog
博客园 - 三生石上(FineUI控件)
量子位
Last Week in AI
Last Week in AI
人人都是产品经理
人人都是产品经理
爱范儿
爱范儿
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
博客园 - 聂微东
V
Visual Studio Blog
Hugging Face - Blog
Hugging Face - Blog
博客园 - 【当耐特】
Jina AI
Jina AI
月光博客
月光博客
L
LangChain Blog

DEV Community

Authentication Security Deep Dive: From Brute Force to Salted Hashing (With Java Examples) Why AI Systems Don’t Fail — They Drift Spilling beans for how i learn for exam😁"Reinforcement Learning Cheat Sheet" I Replaced Chrome with Safari for AI Browser Automation. Here's What Broke (and What Finally Worked) How Python Borrows Other People's Work The $40 Architecture: Processing 1 Billion API Requests with 99.99% Uptime Vibe Coding: A Workflow Guide (From Zero to SaaS) Most webhook security guides protect the wrong side. The scary part is delivery. Headless CMS for TanStack Start: Build a Blog with Cosmic EU Age Verification App "Hacked in 2 Minutes" — What Actually Happened Comfy Cloud’s delete function does not actually remove files Running AI Models on GPU Cloud Servers: A Beginner Guide Event-driven media intelligence with AWS Step Functions and Bedrock I scored 500 AI prompts across 8 quality dimensions — here's what broke How to Call Google Gemini API from Next.js (Free Tier, No Backend Needed) The Portal Protocol: Reclaiming Human Connection in the Age of AI How to Fix Your Team's Scattered Knowledge Problem With a Self-Hosted Forum Intro to tc Cloud Functors: A Graph-First Mental Model for the Modern Cloud Designing Multi-Tenant Backends With Both Ownership and Team Access I Built a Neumorphic CSS Library with 77+ Components — Here's What I Learned PostgreSQL Performance Optimization: Why Connection Pooling Is Critical at Scale Cómo construí un SaaS multi-rubro para gestionar expensas en Argentina con FastAPI + Vue 3 🚀 I Built an Ethical Hacking Scanner Tool – Open Source Project I Replaced /usage and /context in Claude Code With a Single Statusline A Pythonic Way to Handle Emails (IMAP/SMTP) with Auto-Discovery and AI-Ready Design I Collected 8.9 Million Polymarket Price Points — Here's What I Found About How Markets Really Move EcoTrack AI — Carbon Footprint Tracker & Dashboard Everyone's Using AI. No One Agrees How. 5 self-hosted ebook managers worth trying in 2026 Building Your First AI Agent with LangChain: From Chatbot to Autonomous Assistant
Error Boundaries in Next.js App Router — Handling Failure...
Aon infotech · 2026-06-22 · via DEV Community

Most Next.js applications handle the happy path well. A request comes in, data loads, components render, user sees the page. Error handling is where applications often reveal their real quality — and where App Router introduces some nuances worth understanding.

Here's how error boundaries work in App Router, what the error.js file actually does, and the patterns that make failure handling feel intentional rather than afterthought. This is the approach I use in production for a free AI image generator for beginners where graceful degradation matters more than on sites with simpler data flows.


What Changes in App Router

Pages Router had _error.js and getInitialProps for error handling. App Router introduces a different model built on React's Error Boundary concept, with error.js files that can be nested at any level of the route hierarchy.

The key mental model shift: errors are contained at the nearest error.js boundary, not propagated to a global handler. This means you can have different error UIs for different sections of your app.


The error.js File

Place an error.js file in any route segment to catch errors in that segment and its children:

app/
├── layout.js          # Root layout
├── error.js           # Catches errors in root segment
├── page.js
├── dashboard/
│   ├── error.js       # Catches errors in dashboard only
│   ├── layout.js
│   └── page.js
└── blog/
    ├── error.js       # Catches errors in blog only
    └── [slug]/
        └── page.js

// app/error.js
'use client'; // Error components must be Client Components

import { useEffect } from 'react';

export default function Error({ error, reset }) {
  useEffect(() => {
    // Log to error reporting service
    console.error(error);
  }, [error]);

  return (
    <div className="flex flex-col items-center justify-center 
      min-h-[400px] gap-4 p-6 text-center">
      <h2 className="text-xl font-semibold text-foreground">
        Something went wrong
      </h2>
      <p className="text-sm text-muted max-w-sm">
        {error.message || 'An unexpected error occurred.'}
      </p>
      <button
        onClick={reset}
        className="px-4 py-2 bg-orange-500 text-white 
          rounded-full text-sm font-medium 
          hover:bg-orange-600 transition-colors"
      >
        Try again
      </button>
    </div>
  );
}

Important: error.js must be a Client Component ('use client'). React Error Boundaries are a client-side concept — server errors get converted to client-side error events before reaching the boundary.

The reset function retries the failed render. Use it for transient errors (network issues, temporary service failures) rather than permanent ones.


The reset Function — When to Show It

The reset button only makes sense for errors that might succeed on retry:

export default function Error({ error, reset }) {
  // Check if error is retryable
  const isRetryable = error.digest !== 'NEXT_NOT_FOUND';

  return (
    <div className="error-container">
      <h2>Something went wrong</h2>
      <p>{getErrorMessage(error)}</p>

      {isRetryable && (
        <button onClick={reset}>Try again</button>
      )}

      {!isRetryable && (
        <a href="/">Return home</a>
      )}
    </div>
  );
}

function getErrorMessage(error) {
  if (error.message?.includes('fetch')) {
    return 'Failed to load data. Check your connection.';
  }
  if (error.message?.includes('timeout')) {
    return 'Request timed out. Please try again.';
  }
  return 'An unexpected error occurred.';
}


Global Error Handling — global-error.js

The root error.js doesn't catch errors in the root layout.js. For that, you need global-error.js:

// app/global-error.js
'use client';

export default function GlobalError({ error, reset }) {
  return (
    <html>
      <body>
        <div className="global-error">
          <h1>Application Error</h1>
          <p>Something went wrong at the application level.</p>
          <button onClick={reset}>Reload</button>
        </div>
      </body>
    </html>
  );
}

global-error.js replaces the root layout when it renders, so it needs to include <html> and <body> tags. This is the last-resort catch for errors that escape all other boundaries.


Server Component Error Handling

Server components can throw errors directly, but handling them well requires a pattern slightly different from what you might expect:

// app/blog/[slug]/page.js
async function getBlogPost(slug) {
  const response = await fetch(`/api/posts/${slug}`);

  if (response.status === 404) {
    notFound(); // Triggers not-found.js, not error.js
  }

  if (!response.ok) {
    throw new Error(`Failed to fetch post: ${response.status}`);
    // This triggers error.js
  }

  return response.json();
}

export default async function BlogPost({ params }) {
  const post = await getBlogPost(params.slug);
  return <PostContent post={post} />;
}

notFound() vs throw Error(): Use notFound() for expected missing resources — it renders not-found.js and returns a 404 status. Use throw for unexpected failures — it renders error.js.


The not-found.js File

For 404-style errors, not-found.js provides a cleaner separation than error.js:

// app/not-found.js
import Link from 'next/link';

export default function NotFound() {
  return (
    <div className="flex flex-col items-center justify-center 
      min-h-[400px] gap-4 text-center p-6">
      <h2 className="text-2xl font-semibold">Page not found</h2>
      <p className="text-muted text-sm">
        The page you're looking for doesn't exist or has been moved.
      </p>
      <Link 
        href="/"
        className="px-4 py-2 bg-foreground text-background 
          rounded-full text-sm font-medium"
      >
        Return home
      </Link>
    </div>
  );
}


Error Boundaries for Async Operations in Client Components

Client components that fetch data need their own error handling since they're outside the server component error flow:

'use client';
import { useState, useEffect } from 'react';

export function DataFetcher({ url }) {
  const [data, setData] = useState(null);
  const [error, setError] = useState(null);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    async function fetchData() {
      try {
        setLoading(true);
        setError(null);
        const response = await fetch(url);
        if (!response.ok) throw new Error(`HTTP ${response.status}`);
        const json = await response.json();
        setData(json);
      } catch (err) {
        setError(err.message);
      } finally {
        setLoading(false);
      }
    }

    fetchData();
  }, [url]);

  if (loading) return <Skeleton />;

  if (error) return (
    <div className="error-inline">
      <p className="text-sm text-red-500">{error}</p>
      <button 
        onClick={() => fetchData()}
        className="text-xs text-muted hover:text-foreground"
      >
        Retry
      </button>
    </div>
  );

  return <DataDisplay data={data} />;
}


Error Reporting Integration

error.js is the right place to integrate with error monitoring:

'use client';
import { useEffect } from 'react';

export default function Error({ error, reset }) {
  useEffect(() => {
    // Send to your error monitoring service
    reportError({
      message: error.message,
      digest: error.digest, // Next.js server error ID
      stack: error.stack,
      timestamp: new Date().toISOString(),
      path: window.location.pathname,
    });
  }, [error]);

  return (
    // Error UI
  );
}

async function reportError(errorData) {
  try {
    await fetch('/api/errors', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(errorData),
    });
  } catch {
    // Don't let error reporting itself cause more errors
    console.error('Failed to report error:', errorData);
  }
}

The error.digest is particularly useful — it's a hash that Next.js uses to identify server-side errors in logs without exposing sensitive stack traces to the client.


The Error Boundary Hierarchy in Practice

For a real application, the error boundary structure might look like:

global-error.js        ← Last resort, includes <html>
├── error.js           ← Root-level errors
│   ├── (auth)/
│   │   └── error.js   ← Auth-specific errors  
│   ├── dashboard/
│   │   └── error.js   ← Dashboard-specific errors
│   └── api/
│       └── route.js   ← API routes handle their own errors

This lets you show relevant error messages — "Your session expired, please log in again" for auth errors, "Failed to load dashboard data" for dashboard errors — rather than a generic "something went wrong" for everything.


What I Built This On

This error handling pattern runs in production on pixova.io. The generation pipeline has several async steps where things can go wrong — API timeouts, inference failures, upload errors — and the error boundary setup means failures at each step show appropriate messages rather than breaking the whole page.

The reset button is particularly important for generation tools where users are mid-workflow when something fails — it retries without losing the prompt they just wrote.

Questions on specific error scenarios? Drop them in the comments.