Lesson 35 of 81
Dropping an error.tsx file into a folder feels like framework magic, but it's wired from an ordinary React primitive underneath — which means it inherits that primitive's exact rules for what it does and doesn't catch.
13 min read
Every option explains itself — why the right one is right, and why each wrong one is wrong. Miss one and it comes back later on a spaced-repetition schedule.
This topic is about judgment and practical tradeoffs more than a single right answer — a mock interview is the better way to test yourself on it.
Be interviewed on this →Hints without spoilers, and a plain explanation of why a wrong answer was wrong — answered only from this lesson. Free account, no card.
Sign up freeWhat interviewers actually ask about this, and what a strong answer has to cover.
A strong answer covers
Likely follow-ups
error.tsx isn't a Next.js-invented error-handling mechanism — it's React's error boundary API (the class component lifecycle methods static getDerivedStateFromError and componentDidCatch), which Next.js instantiates and wires around your route segment automatically. You never write the class yourself; you write a function component, and Next.js generates the boundary class around it behind the scenes. But because it's a real error boundary and not something bespoke, it inherits React error boundaries' actual, specific rule: it only catches errors thrown during rendering, in lifecycle methods, or in constructors of the component tree below it.
That rule has teeth. It means error.tsx does not catch an error thrown inside a <button onClick={...}> handler, an error thrown inside a setTimeout callback, or an error thrown in a promise that rejects after render already finished successfully — none of those are "during rendering." Code reaching for error.tsx to catch a click-handler bug will find it does nothing, and the error just surfaces as an unhandled exception in the console instead.
// app/checkout/page.tsx
export default async function CheckoutPage() {
const cart = await getCart(); // throws during render → error.tsx catches this
if (cart.total < 0) {
throw new Error("Corrupted cart total"); // also during render → caught
}
return <CheckoutForm cart={cart} />;
}
// components/checkout-form.tsx
"use client";
export function CheckoutForm({ cart }: { cart: Cart }) {
function handleSubmit() {
if (!cart.paymentMethodId) {
throw new Error("No payment method"); // thrown in an event handler → NOT caught by error.tsx
}
submitOrder(cart);
}
return <button onClick={handleSubmit}>Place order</button>;
}
The first two throws happen while CheckoutPage is rendering — React's rendering machinery is directly in the call stack, which is exactly the condition an error boundary is built to intercept. The third throw happens inside a browser event callback, well after rendering already completed; nothing about React's render cycle is on the stack when it fires, so there's no boundary positioned to catch it. That one needs an explicit try/catch inside handleSubmit itself, or a manually-shown error state — error.tsx simply never sees it.
error.tsx's own render logic throws, that error propagates to the next boundary up the tree (typically an ancestor segment's error.tsx, or eventually global-error.tsx), not back into the boundary that failed.digest on a production error is deliberately opaque — that's a security property, not a bug to work around. Trying to parse meaning out of it client-side is a dead end by design; the point is to send you to server logs instead of exposing internals to any visitor with dev tools open.error.tsx at a route's top level means one failing widget takes out the whole page's content area. Wrapping just the risky piece — say, a Suspense-streamed third-party widget — in its own nested segment (or a manually-authored React error boundary component around just that subtree) contains the blast radius to the one thing that actually failed.error.tsx catches errors from Server Components rendered within its segment too, not just Client Component errors — the boundary doesn't care which side of the server/client line the throw came from, only whether it happened during that segment's render."A user reports a blank error screen after clicking 'Place order,' but your error.tsx never seems to log anything for it — what's actually going on?" is a real debugging scenario that separates a candidate who's memorized "error.tsx catches errors" from one who understands which errors, specifically, a React error boundary was ever designed to catch — the click handler is the tell, and recognizing it immediately (rather than staring at error.tsx looking for a bug that isn't there) is the actual signal.