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.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: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.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}'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.
Props:
error: Error object containing information about what went wrongreset: Function that allows retrying the render of the component that produced the errorreset() function: Allows users to retry an operation without reloading the entire page.
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.tsxWe 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}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}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}Errors during data fetching in Next.js components can be handled with error.tsx, but it's worth remembering a few nuances:
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}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}Appropriate granularity - use error.tsx at levels that provide meaningful error isolation, but don't create too many components.
Useful messages - provide user-friendly messages and clear instructions for next steps.
Recovery options - offer ways to recover from the situation, such as a "Try again" button (using the
reset function).Tracking and monitoring - always log errors to monitoring systems so you can track and resolve issues.
Testing - test various error scenarios to make sure your error boundaries respond correctly.
Accessibility - ensure that error messages are also accessible to users of assistive technologies.
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.tsxCorrect 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.tsxBelow 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}error.tsx components in Next.js are a powerful error handling tool that:
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.