We use cookies to enhance your experience on the site
CodeWorlds

error.tsx components for error handling

When building Next.js applications, one important aspect is handling failure scenarios properly. The Next.js framework offers a simple yet powerful mechanism that allows easy isolation and handling of errors in an application -

error.tsx
components. In this module we'll look at how to effectively use these components to improve the resilience and user experience of our application.

What is the error.tsx component?

The

error.tsx
component is a special file in the Next.js application structure that acts as an "error boundary" for a given segment of the application. When an error occurs in that segment or its children:

  1. Next.js automatically catches the error
  2. Renders the error.tsx component instead of the component that produced the error
  3. Isolates the error to the specific segment, allowing the rest of the application to continue to work normally

How does error.tsx work in Next.js App Router?

In the App Router architecture, we can place

error.tsx
components at any level of the directory structure, which allows for very precise specification of error boundaries.

Basic implementation

Here's what a simple error.tsx component looks like:

1'use client'; // Must be a client component
2
3import { useEffect } from 'react';
4
5interface ErrorComponentProps {
6  error: Error & { digest?: string };
7  reset: () => void;
8}
9
10export default function Error({ error, reset }: ErrorComponentProps) {
11  useEffect(() => {
12    // Optionally report the error to a monitoring service
13    console.error('An unexpected error occurred:', error);
14  }, [error]);
15
16  return (
17    <div className="p-6 max-w-sm mx-auto bg-white rounded-xl shadow-md">
18      <h2 className="text-xl font-bold text-red-600 mb-4">Something went wrong!</h2>
19      <p className="text-gray-700 mb-4">
20        Sorry, an unexpected error occurred. Our team has been notified.
21      </p>
22      <button
23        onClick={reset}
24        className="px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600"
25      >
26        Try again
27      </button>
28    </div>
29  );
30}

Key elements:

  1. 'use client' marker - the error.tsx component must be marked as a client component, even if the rest of the application uses server-side rendering.

  2. Props:

    • error
      : Error object containing information about what went wrong
    • reset
      : Function that allows retrying the render of the component that produced the error
  3. reset() function: Allows users to retry an operation without reloading the entire page.

Error handling strategies with error.tsx

1. Hierarchical error handling

Next.js allows you to create error.tsx components at various levels of the directory structure, enabling precise control over how errors are handled in different parts of the application.

1app/
2├── error.tsx          # Global fallback for errors across the whole application
3├── layout.tsx
4├── page.tsx
5├── dashboard/
6│   ├── error.tsx      # Error handling for the entire dashboard
7│   ├── layout.tsx
8│   ├── page.tsx
9│   └── settings/
10│       ├── error.tsx  # Specific to the settings section
11│       └── page.tsx
12└── profile/
13    ├── error.tsx      # Specific to the user profile
14    └── page.tsx

2. Detailed error messages in the development environment

We can adapt the error.tsx component to display more detailed error information in the development environment:

1'use client';
2
3export default function Error({ error, reset }) {
4  const isDevelopment = process.env.NODE_ENV === 'development';
5
6  return (
7    <div>
8      <h2>Something went wrong!</h2>
9
10      {isDevelopment && (
11        <div className="bg-gray-100 p-4 rounded my-4">
12          <h3 className="font-bold">Error details (dev mode only):</h3>
13          <p className="font-mono">{error.message}</p>
14          <pre className="overflow-auto text-sm mt-2">{error.stack}</pre>
15        </div>
16      )}
17
18      <button onClick={reset}>Try again</button>
19    </div>
20  );
21}

3. Different error types, different messages

We can customize the look and messages depending on the error type:

1'use client';
2
3import { useEffect } from 'react';
4
5export default function Error({ error, reset }) {
6  // Logging the error to the monitoring system
7  useEffect(() => {
8    console.error(error);
9    // logErrorToService(error);
10  }, [error]);
11
12  // Determine error type based on message or other properties
13  const is404 = error.message?.includes('not found') || error.statusCode === 404;
14  const isAuthentication = error.message?.includes('unauthorized') || error.statusCode === 401;
15
16  return (
17    <div className="p-6 mx-auto max-w-md">
18      {is404 ? (
19        <>
20          <h2 className="text-xl font-bold">Resource not found</h2>
21          <p>Sorry, the requested resource does not exist.</p>
22        </>
23      ) : isAuthentication ? (
24        <>
25          <h2 className="text-xl font-bold">Authentication required</h2>
26          <p>You must log in to access this resource.</p>
27          <button className="mt-4 px-4 py-2 bg-blue-500 text-white rounded">
28            Log in
29          </button>
30        </>
31      ) : (
32        <>
33          <h2 className="text-xl font-bold text-red-600">Unexpected error</h2>
34          <p>Sorry, an unexpected problem occurred.</p>
35        </>
36      )}
37
38      <button
39        onClick={reset}
40        className="mt-4 px-4 py-2 bg-gray-200 rounded hover:bg-gray-300"
41      >
42        Try again
43      </button>
44    </div>
45  );
46}

Integration with error monitoring systems

In production applications, collecting error information is essential. error.tsx components can be integrated with external error monitoring systems such as Sentry, LogRocket, or New Relic:

1'use client';
2
3import { useEffect } from 'react';
4import * as Sentry from '@sentry/nextjs';
5
6export default function Error({ error, reset }) {
7  useEffect(() => {
8    // Send error information to Sentry
9    Sentry.captureException(error);
10  }, [error]);
11
12  return (
13    <div>
14      <h2>Something went wrong!</h2>
15      <p>Our team has been notified of the problem.</p>
16      <button onClick={reset}>Try again</button>
17    </div>
18  );
19}

Handling API and fetch errors

Errors during data fetching in Next.js components can be handled with error.tsx, but it's worth remembering a few nuances:

In server components

1// app/products/page.tsx
2async function getProducts() {
3  const res = await fetch('https://api.example.com/products');
4
5  if (!res.ok) {
6    // This error will be caught by the nearest error.tsx
7    throw new Error('Failed to fetch products');
8  }
9
10  return res.json();
11}
12
13export default async function ProductsPage() {
14  const products = await getProducts();
15
16  return (
17    <div>
18      <h1>Our products</h1>
19      <ul>
20        {products.map(product => (
21          <li key={product.id}>{product.name}</li>
22        ))}
23      </ul>
24    </div>
25  );
26}

In client components

1'use client';
2
3import { useState, useEffect } from 'react';
4import { useRouter } from 'next/navigation';
5
6export default function ClientProductsPage() {
7  const [products, setProducts] = useState([]);
8  const [error, setError] = useState(null);
9  const router = useRouter();
10  
11  useEffect(() => {
12    async function fetchProducts() {
13      try {
14        const res = await fetch('/api/products');
15        
16        if (!res.ok) {
17          throw new Error('Data fetch error');
18        }
19
20        const data = await res.json();
21        setProducts(data);
22      } catch (err) {
23        setError(err.message);
24        // Optionally - we can manually invoke the nearest error.tsx
25        // throw err; // This will make Next.js use the nearest error.tsx
26      }
27    }
28
29    fetchProducts();
30  }, []);
31
32  if (error) {
33    return (
34      <div className="p-4 bg-red-50 border border-red-200 rounded">
35        <h2 className="text-red-600 font-bold">An error occurred</h2>
36        <p>{error}</p>
37        <button onClick={() => router.refresh()}>Refresh page</button>
38      </div>
39    );
40  }
41
42  return (
43    <div>
44      <h1>Products (Client)</h1>
45      {/* ... */}
46    </div>
47  );
48}

Good practices when working with error.tsx

  1. Appropriate granularity - use error.tsx at levels that provide meaningful error isolation, but don't create too many components.

  2. Useful messages - provide user-friendly messages and clear instructions for next steps.

  3. Recovery options - offer ways to recover from the situation, such as a "Try again" button (using the

    reset
    function).

  4. Tracking and monitoring - always log errors to monitoring systems so you can track and resolve issues.

  5. Testing - test various error scenarios to make sure your error boundaries respond correctly.

  6. Accessibility - ensure that error messages are also accessible to users of assistive technologies.

Catching errors from layout.tsx

Error boundaries from error.tsx do not catch errors from

layout.tsx
components in the same segment. To handle errors in layouts, you must create an error.tsx component in the parent segment.

1app/
2├── dashboard/
3│   ├── layout.tsx  // Errors here are NOT caught by error.tsx at the same level
4│   ├── error.tsx   // This will not catch errors from layout.tsx
5│   └── page.tsx

Correct structure for catching errors from layout.tsx:

1app/
2├── error.tsx     // This will catch errors from dashboard/layout.tsx
3├── dashboard/
4│   ├── layout.tsx
5│   └── page.tsx

Example of a complete implementation

Below is an example of a more advanced error.tsx component that handles various error types, recovery options, and integration with an external monitoring system:

1'use client';
2
3import { useEffect, useState } from 'react';
4import Link from 'next/link';
5import { useRouter } from 'next/navigation';
6
7// Error types that we can handle specially
8enum ErrorType {
9  NOT_FOUND = 'NOT_FOUND',
10  UNAUTHORIZED = 'UNAUTHORIZED',
11  FORBIDDEN = 'FORBIDDEN',
12  TIMEOUT = 'TIMEOUT',
13  SERVER_ERROR = 'SERVER_ERROR',
14  UNKNOWN = 'UNKNOWN'
15}
16
17interface ErrorComponentProps {
18  error: Error & { 
19    digest?: string;
20    statusCode?: number;
21  };
22  reset: () => void;
23}
24
25export default function Error({ error, reset }: ErrorComponentProps) {
26  const router = useRouter();
27  const [errorType, setErrorType] = useState<ErrorType>(ErrorType.UNKNOWN);
28  const [isReporting, setIsReporting] = useState(false);
29  
30  // Determine the error type
31  useEffect(() => {
32    if (error.message?.toLowerCase().includes('not found') || error.statusCode === 404) {
33      setErrorType(ErrorType.NOT_FOUND);
34    } else if (error.message?.toLowerCase().includes('unauthorized') || error.statusCode === 401) {
35      setErrorType(ErrorType.UNAUTHORIZED);
36    } else if (error.message?.toLowerCase().includes('forbidden') || error.statusCode === 403) {
37      setErrorType(ErrorType.FORBIDDEN);
38    } else if (error.message?.toLowerCase().includes('timeout') || error.message?.includes('timed out')) {
39      setErrorType(ErrorType.TIMEOUT);
40    } else if (error.statusCode >= 500 && error.statusCode < 600) {
41      setErrorType(ErrorType.SERVER_ERROR);
42    }
43  }, [error]);
44  
45  // Error reporting
46  useEffect(() => {
47    // Logging to console in dev
48    console.error('Application error:', error);
49
50    // In a real application, we would send this to an external service
51    async function reportError() {
52      try {
53        setIsReporting(true);
54        // Example implementation
55        // await fetch('/api/error-reporting', {
56        //   method: 'POST',
57        //   headers: { 'Content-Type': 'application/json' },
58        //   body: JSON.stringify({
59        //     message: error.message,
60        //     stack: process.env.NODE_ENV === 'development' ? error.stack : undefined,
61        //     url: window.location.href,
62        //     type: errorType,
63        //     timestamp: new Date().toISOString()
64        //   })
65        // });
66        await new Promise(resolve => setTimeout(resolve, 1000)); // Simulation
67      } catch (e) {
68        console.error('Failed to report error:', e);
69      } finally {
70        setIsReporting(false);
71      }
72    }
73
74    reportError();
75  }, [error, errorType]);
76
77  // Render different messages depending on the error type
78  function renderErrorContent() {
79    switch (errorType) {
80      case ErrorType.NOT_FOUND:
81        return (
82          <>
83            <h2 className="text-2xl font-bold text-gray-800 mb-3">Resource not found</h2>
84            <p className="text-gray-600 mb-4">
85              The page you're looking for doesn't exist or has been moved.
86            </p>
87            <div className="flex space-x-4">
88              <button
89                onClick={() => router.back()}
90                className="px-4 py-2 bg-gray-200 rounded hover:bg-gray-300"
91              >
92                Back
93              </button>
94              <Link href="/" className="px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600">
95                Home page
96              </Link>
97            </div>
98          </>
99        );
100
101      case ErrorType.UNAUTHORIZED:
102        return (
103          <>
104            <h2 className="text-2xl font-bold text-gray-800 mb-3">Login required</h2>
105            <p className="text-gray-600 mb-4">
106              You must log in to access this page.
107            </p>
108            <Link
109              href="/login"
110              className="px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600"
111            >
112              Log in
113            </Link>
114          </>
115        );
116
117      case ErrorType.FORBIDDEN:
118        return (
119          <>
120            <h2 className="text-2xl font-bold text-gray-800 mb-3">Access denied</h2>
121            <p className="text-gray-600 mb-4">
122              You don't have permission to access this page or resource.
123            </p>
124            <button
125              onClick={() => router.back()}
126              className="px-4 py-2 bg-gray-200 rounded hover:bg-gray-300"
127            >
128              Back
129            </button>
130          </>
131        );
132
133      case ErrorType.TIMEOUT:
134        return (
135          <>
136            <h2 className="text-2xl font-bold text-gray-800 mb-3">Request timed out</h2>
137            <p className="text-gray-600 mb-4">
138              The operation took too long. Check your internet connection and try again.
139            </p>
140            <button
141              onClick={reset}
142              className="px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600"
143            >
144              Try again
145            </button>
146          </>
147        );
148
149      case ErrorType.SERVER_ERROR:
150        return (
151          <>
152            <h2 className="text-2xl font-bold text-gray-800 mb-3">Server error</h2>
153            <p className="text-gray-600 mb-4">
154              Sorry, an error occurred on the server side. Our team has been notified.
155            </p>
156            <button
157              onClick={reset}
158              className="px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600"
159            >
160              Try again
161            </button>
162          </>
163        );
164
165      default:
166        return (
167          <>
168            <h2 className="text-2xl font-bold text-gray-800 mb-3">Something went wrong</h2>
169            <p className="text-gray-600 mb-4">
170              Sorry, an unexpected error occurred. {isReporting ? 'Reporting the issue...' : 'Our team has been notified.'}
171            </p>
172            <div className="flex space-x-4">
173              <button
174                onClick={reset}
175                className="px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600"
176              >
177                Try again
178              </button>
179              <button
180                onClick={() => router.refresh()}
181                className="px-4 py-2 bg-gray-200 rounded hover:bg-gray-300"
182              >
183                Refresh page
184              </button>
185            </div>
186          </>
187        );
188    }
189  }
190
191  // Render for development mode - additional error info
192  function renderDevModeInfo() {
193    if (process.env.NODE_ENV !== 'development') return null;
194
195    return (
196      <div className="mt-8 p-4 bg-gray-100 rounded-lg border border-gray-300">
197        <h3 className="text-sm font-bold uppercase text-gray-500 mb-2">
198          Error info (dev mode only)
199        </h3>
200        <p className="font-mono text-sm mb-2">{error.message}</p>
201        {error.digest && (
202          <p className="font-mono text-xs text-gray-500 mb-2">Digest: {error.digest}</p>
203        )}
204        {error.stack && (
205          <pre className="mt-2 p-2 bg-gray-800 text-gray-200 rounded overflow-x-auto text-xs">
206            {error.stack}
207          </pre>
208        )}
209      </div>
210    );
211  }
212  
213  return (
214    <div className="min-h-[400px] flex flex-col items-center justify-center p-6">
215      <div className="w-full max-w-md bg-white rounded-lg shadow-lg p-8 text-center">
216        {renderErrorContent()}
217        {renderDevModeInfo()}
218      </div>
219    </div>
220  );
221}

Summary

error.tsx components in Next.js are a powerful error handling tool that:

  1. Isolate errors to specific segments of the application
  2. Allow presenting user-friendly messages
  3. Enable attempting to recover without reloading the page
  4. Can be customized for different error types
  5. Allow integration with error monitoring systems

Thanks to them, you can build more resilient applications that elegantly handle failure scenarios, improving the overall user experience and reducing frustration caused by unexpected problems.

Go to CodeWorlds