Next.js course Β· Module 6: Forms and Server Actions
error.tsx components for error handling
In this lesson9
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:
- Next.js automatically catches the error
- Renders the error.tsx component instead of the component that produced the error
- 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:
'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 error
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.tsx2. 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
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
resetfunction).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.
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.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.tsxExample 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:
- Isolate errors to specific segments of the application
- Allow presenting user-friendly messages
- Enable attempting to recover without reloading the page
- Can be customized for different error types
- 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.
Code for this lesson: App.tsx
1import React, { useState, Component, ReactNode } from 'react';
2
3// Simulation of error.tsx in the Next.js App Router
4// Every segment can have its own error boundary
5
6// Simulated error boundary (like error.tsx in Next.js)
7class ErrorBoundary extends Component<
8 { children: ReactNode; fallback: (error: Error, reset: () => void) => ReactNode },
9 { error: Error | null }
10> {
11 state: { error: Error | null } = { error: null };
12 static getDerivedStateFromError(error: Error) { return { error }; }
13 reset = () => this.setState({ error: null });
14 render() {
15 if (this.state.error) return this.props.fallback(this.state.error, this.reset);
16 return this.props.children;
17 }
18}
19
20// error.tsx component - displayed when an error occurs
21function ErrorComponent({ error, reset }: { error: Error; reset: () => void }) {
22 return (
23 <div style={{
24 background: '#3e1010', border: '1px solid #f44336', borderRadius: 8,
25 padding: 16, margin: '8px 0'
26 }}>
27 <h3 style={{ color: '#f44336', margin: '0 0 8px' }}>error.tsx</h3>
28 <p style={{ color: '#ffcdd2', fontSize: 13 }}>{error.message}</p>
29 <button onClick={reset} style={{
30 background: '#f44336', color: '#fff', border: 'none', borderRadius: 4,
31 padding: '6px 12px', cursor: 'pointer', fontFamily: 'monospace', marginTop: 8
32 }}>
33 Try again (reset)
34 </button>
35 </div>
36 );
37}
38
39// Component that can throw an error
40function DangerousWidget({ label }: { label: string }) {
41 const [shouldError, setShouldError] = useState(false);
42 if (shouldError) throw new Error(`Error in component "${label}"!`);
43 return (
44 <div style={{ background: '#1a2744', borderRadius: 8, padding: 16, border: '1px solid #64ffda' }}>
45 <h4 style={{ color: '#64ffda', margin: '0 0 8px' }}>{label}</h4>
46 <p style={{ color: '#aaa', fontSize: 12 }}>This component works correctly.</p>
47 <button onClick={() => setShouldError(true)} style={{
48 background: '#ff5722', color: '#fff', border: 'none', borderRadius: 4,
49 padding: '4px 10px', cursor: 'pointer', fontFamily: 'monospace', fontSize: 11
50 }}>
51 Trigger error
52 </button>
53 </div>
54 );
55}
56
57export default function ErrorTsxDemo() {
58 return (
59 <div style={{ background: '#0f0f23', minHeight: '100vh', padding: 20, color: '#e0e0e0', fontFamily: 'monospace' }}>
60 <h1 style={{ color: '#64ffda' }}>error.tsx in Next.js</h1>
61 <p style={{ color: '#888', fontSize: 13, marginBottom: 16 }}>
62 Every segment can have its own error.tsx. The error is isolated - the rest of the app keeps working.
63 </p>
64
65 <div style={{ display: 'grid', gap: 12 }}>
66 <div style={{ background: '#0d1117', borderRadius: 8, padding: 12 }}>
67 <div style={{ color: '#888', fontSize: 11, marginBottom: 8 }}>app/dashboard/error.tsx</div>
68 <ErrorBoundary fallback={(error, reset) => <ErrorComponent error={error} reset={reset} />}>
69 <DangerousWidget label="Dashboard Widget" />
70 </ErrorBoundary>
71 </div>
72
73 <div style={{ background: '#0d1117', borderRadius: 8, padding: 12 }}>
74 <div style={{ color: '#888', fontSize: 11, marginBottom: 8 }}>app/profile/error.tsx</div>
75 <ErrorBoundary fallback={(error, reset) => <ErrorComponent error={error} reset={reset} />}>
76 <DangerousWidget label="Profile Widget" />
77 </ErrorBoundary>
78 </div>
79
80 <div style={{ background: '#0d1117', borderRadius: 8, padding: 12 }}>
81 <div style={{ color: '#888', fontSize: 11, marginBottom: 8 }}>app/settings/error.tsx (independent)</div>
82 <ErrorBoundary fallback={(error, reset) => <ErrorComponent error={error} reset={reset} />}>
83 <DangerousWidget label="Settings Widget" />
84 </ErrorBoundary>
85 </div>
86 </div>
87 </div>
88 );
89}Spotted a mistake in this lesson?
Check yourself
Answer the questions from this lesson. Pick an answer to see right away whether it is correct.
1. ARIA labels in forms help with: