Good Code
The good version uses a route-level error.tsx Client Component so unexpected render failures are contained and retryable.
Lesson 07
Give dynamic routes immediate loading UI and route-level recovery.
"use client";
import { useEffect } from "react";
export default function ReviewsError({
error,
unstable_retry,
}: {
error: Error & { digest?: string };
unstable_retry: () => void;
}) {
useEffect(() => {
console.error(error);
}, [error]);
// A route error boundary can log, show fallback UI, and trigger retry.
return (
<section role="alert">
<h2>Reviews could not load.</h2>
<button type="button" onClick={() => unstable_retry()}>
Try again
</button>
</section>
);
}import { getReviews } from "@/lib/reviews";
export default async function ReviewsPage() {
// Catching every error in the page hides failures from the route boundary.
try {
const reviews = await getReviews();
return reviews.map((review) => <p key={review.id}>{review.title}</p>);
} catch {
return <p>Something went wrong.</p>;
}
}The good version uses a route-level error.tsx Client Component so unexpected render failures are contained and retryable.
The bad version catches every exception inside the page, hiding failures from the route boundary and leaving navigation without a meaningful loading strategy.