乐闻世界logo
搜索文章和话题

How to handle errors in Next.js?

2月17日 23:31

Next.js provides multiple error handling mechanisms to help developers build robust applications. Understanding these mechanisms is crucial for building reliable user experiences.

Error Handling Layers

Next.js error handling is divided into multiple layers:

  1. Component-level errors: Using Error Boundary
  2. Route-level errors: Using error.js file
  3. Server-level errors: Using global error handling
  4. API errors: Handling in API Routes

Route-level Error Handling (App Router)

error.js File

In App Router, the error.js file is used to catch errors in route segments and their child components.

javascript
// app/error.js 'use client'; export default function Error({ error, reset, }) { return ( <div> <h2>Something went wrong!</h2> <p>{error.message}</p> <button onClick={() => reset()}>Try again</button> </div> ); } export default function GlobalError({ error, reset }) { return ( <html> <body> <h2>Something went wrong!</h2> <button onClick={reset}>Try again</button> </body> </html> ); }

Nested Error Boundaries

javascript
// app/dashboard/error.js 'use client'; export default function DashboardError({ error, reset }) { return ( <div> <h2>Dashboard Error</h2> <p>{error.message}</p> <button onClick={reset}>Retry</button> </div> ); } // app/dashboard/settings/error.js 'use client'; export default function SettingsError({ error, reset }) { return ( <div> <h2>Settings Error</h2> <p>{error.message}</p> <button onClick={reset}>Retry</button> </div> ); }

Pages Router Error Handling

Custom Error Pages

javascript
// pages/404.js export default function Custom404() { return ( <div> <h1>404 - Page Not Found</h1> <p>The page you're looking for doesn't exist.</p> <Link href="/">Go Home</Link> </div> ); } // pages/500.js export default function Custom500() { return ( <div> <h1>500 - Server Error</h1> <p>Something went wrong on our end.</p> <Link href="/">Go Home</Link> </div> ); } // pages/_error.js export default function Error({ statusCode, err }) { return ( <div> <p> {statusCode ? `An error ${statusCode} occurred on server` : 'An error occurred on client'} </p> <p>{err?.message}</p> </div> ); } Error.getInitialProps = ({ res, err }) => { const statusCode = res ? res.statusCode : err ? err.statusCode : 404; return { statusCode }; };

getServerSideProps Error Handling

javascript
export async function getServerSideProps(context) { try { const data = await fetchData(); if (!data) { return { notFound: true, // Returns 404 page }; } return { props: { data }, }; } catch (error) { return { props: { error: error.message }, }; } }

Server Component Error Handling

Using try-catch

javascript
async function ProductPage({ params }) { try { const product = await fetchProduct(params.id); if (!product) { notFound(); // Returns 404 page } return <ProductDetails product={product} />; } catch (error) { console.error('Failed to fetch product:', error); throw error; // Triggers nearest error boundary } }

Using notFound()

javascript
import { notFound } from 'next/navigation'; async function BlogPost({ params }) { const post = await getPostBySlug(params.slug); if (!post) { notFound(); // Returns 404 page } return <PostContent post={post} />; }

Custom not-found Page

javascript
// app/not-found.js export default function NotFound() { return ( <div> <h2>Page Not Found</h2> <p>The page you're looking for doesn't exist.</p> <Link href="/">Go Home</Link> </div> ); } // app/blog/not-found.js export default function BlogNotFound() { return ( <div> <h2>Blog Post Not Found</h2> <p>The blog post you're looking for doesn't exist.</p> <Link href="/blog">Back to Blog</Link> </div> ); }

API Routes Error Handling

Error Response Format

javascript
// app/api/hello/route.js export async function GET(request) { try { const data = await fetchData(); return Response.json(data); } catch (error) { console.error('API Error:', error); return Response.json( { error: 'Internal Server Error', message: error.message, timestamp: new Date().toISOString(), }, { status: 500 } ); } }

Validation Errors

javascript
import { z } from 'zod'; const schema = z.object({ email: z.string().email(), password: z.string().min(8), }); export async function POST(request) { try { const body = await request.json(); // Validate input const validated = schema.parse(body); // Process request const result = await createUser(validated); return Response.json(result, { status: 201 }); } catch (error) { if (error instanceof z.ZodError) { return Response.json( { error: 'Validation Error', details: error.errors, }, { status: 400 } ); } return Response.json( { error: 'Internal Server Error' }, { status: 500 } ); } }

Authentication Errors

javascript
export async function GET(request) { const token = request.headers.get('authorization'); if (!token) { return Response.json( { error: 'Unauthorized' }, { status: 401 } ); } try { const user = await verifyToken(token); return Response.json({ user }); } catch (error) { return Response.json( { error: 'Invalid token' }, { status: 401 } ); } }

Client-side Error Handling

Error Boundary Component

javascript
'use client'; import { Component, ReactNode } from 'react'; interface Props { children: ReactNode; fallback?: ReactNode; } interface State { hasError: boolean; error?: Error; } export class ErrorBoundary extends Component<Props, State> { constructor(props: Props) { super(props); this.state = { hasError: false }; } static getDerivedStateFromError(error: Error): State { return { hasError: true, error }; } componentDidCatch(error: Error, errorInfo: any) { console.error('Error caught by boundary:', error, errorInfo); // Send error to logging service logErrorToService(error, errorInfo); } render() { if (this.state.hasError) { return this.props.fallback || ( <div> <h2>Something went wrong</h2> <p>{this.state.error?.message}</p> <button onClick={() => this.setState({ hasError: false })}> Try again </button> </div> ); } return this.props.children; } }

Using Error Boundary

javascript
'use client'; import { ErrorBoundary } from '@/components/ErrorBoundary'; export default function Page() { return ( <ErrorBoundary fallback={ <div> <h2>Component Error</h2> <button onClick={() => window.location.reload()}> Reload Page </button> </div> } > <RiskyComponent /> </ErrorBoundary> ); }

Async Error Handling

javascript
'use client'; import { useEffect } from 'react'; export default function AsyncComponent() { useEffect(() => { const fetchData = async () => { try { const data = await fetch('/api/data').then(r => r.json()); console.log(data); } catch (error) { console.error('Fetch error:', error); // Show error message showErrorToast('Failed to fetch data'); } }; fetchData(); }, []); return <div>Loading...</div>; }

Global Error Handling

Using window.onerror

javascript
// app/layout.js 'use client'; import { useEffect } from 'react'; export default function RootLayout({ children }) { useEffect(() => { const handleError = (event: ErrorEvent) => { console.error('Global error:', event.error); // Send to error tracking service logErrorToService({ message: event.message, filename: event.filename, lineno: event.lineno, colno: event.colno, error: event.error, }); }; window.addEventListener('error', handleError); return () => { window.removeEventListener('error', handleError); }; }, []); return ( <html lang="en"> <body>{children}</body> </html> ); }

Using window.onunhandledrejection

javascript
useEffect(() => { const handleUnhandledRejection = (event: PromiseRejectionEvent) => { console.error('Unhandled promise rejection:', event.reason); // Send to error tracking service logErrorToService({ type: 'unhandledRejection', reason: event.reason, }); // Prevent default console error event.preventDefault(); }; window.addEventListener('unhandledrejection', handleUnhandledRejection); return () => { window.removeEventListener('unhandledrejection', handleUnhandledRejection); }; }, []);

Error Logging and Monitoring

Using Sentry

javascript
// sentry.client.config.js import * as Sentry from '@sentry/nextjs'; Sentry.init({ dsn: process.env.NEXT_PUBLIC_SENTRY_DSN, environment: process.env.NODE_ENV, tracesSampleRate: 1.0, replaysSessionSampleRate: 0.1, replaysOnErrorSampleRate: 1.0, }); // sentry.server.config.js import * as Sentry from '@sentry/nextjs'; Sentry.init({ dsn: process.env.NEXT_PUBLIC_SENTRY_DSN, environment: process.env.NODE_ENV, tracesSampleRate: 1.0, });

Custom Error Logging

javascript
// lib/errorLogger.js export function logError(error: Error, context?: any) { const errorData = { message: error.message, stack: error.stack, timestamp: new Date().toISOString(), context, }; // Send to logging service if (typeof window !== 'undefined') { // Client-side fetch('/api/log-error', { method: 'POST', body: JSON.stringify(errorData), }); } else { // Server-side console.error('Server error:', errorData); } } export function logApiError(error: any, request: Request) { const errorData = { url: request.url, method: request.method, error: error.message, stack: error.stack, timestamp: new Date().toISOString(), }; console.error('API error:', errorData); }

Best Practices

1. Provide Helpful Error Messages

javascript
// ✅ Good practice export default function Error({ error, reset }) { return ( <div> <h2>Something went wrong</h2> <p>We couldn't load the page. Please try again.</p> <button onClick={reset}>Try again</button> <Link href="/">Go Home</Link> </div> ); } // ❌ Bad practice export default function Error({ error }) { return ( <div> <p>{error.message}</p> </div> ); }

2. Log Error Context

javascript
// ✅ Good practice try { const user = await getUser(userId); const posts = await getPosts(user.id); return { user, posts }; } catch (error) { logError(error, { userId, action: 'fetchUserPosts' }); throw error; } // ❌ Bad practice try { const user = await getUser(userId); const posts = await getPosts(user.id); return { user, posts }; } catch (error) { console.error(error); throw error; }

3. Provide Recovery Options

javascript
// ✅ Good practice export default function Error({ error, reset }) { return ( <div> <h2>Something went wrong</h2> <button onClick={reset}>Try again</button> <button onClick={() => window.location.href = '/'}> Go Home </button> </div> ); } // ❌ Bad practice export default function Error({ error }) { return ( <div> <h2>Something went wrong</h2> <p>Please refresh the page</p> </div> ); }

4. Distinguish Development and Production

javascript
export default function Error({ error, reset }) { const isDev = process.env.NODE_ENV === 'development'; return ( <div> <h2>Something went wrong</h2> {isDev ? ( <details> <summary>Error Details</summary> <pre>{error.message}</pre> <pre>{error.stack}</pre> </details> ) : ( <p>We're sorry for the inconvenience. Please try again.</p> )} <button onClick={reset}>Try again</button> </div> ); }

5. Use Type-safe Error Handling

javascript
// types/errors.ts export class AppError extends Error { constructor( message: string, public statusCode: number = 500, public code?: string ) { super(message); this.name = 'AppError'; } } export class NotFoundError extends AppError { constructor(message: string = 'Resource not found') { super(message, 404, 'NOT_FOUND'); } } export class ValidationError extends AppError { constructor(message: string = 'Validation failed') { super(message, 400, 'VALIDATION_ERROR'); } } // Usage export async function getProduct(id: string) { const product = await db.product.findUnique({ where: { id } }); if (!product) { throw new NotFoundError(`Product ${id} not found`); } return product; }

By properly using these error handling mechanisms, you can build robust and reliable Next.js applications that provide excellent user experience.

标签:Next.js