Lesson 63 of 81
error.tsx and the server-side error hook aren't two ways to do the same job — they cover different failure surfaces, and there's a third surface neither of them touches at all.
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
It's tempting to treat error.tsx and the instrumentation file's server-error hook as redundant — "we already have error boundaries, why do we also need a monitoring hook?" They actually answer two different questions. error.tsx answers "what does the user see when something breaks?" The server-error hook answers "does anyone on the team find out it broke at all?" A route can nail the first and completely fail the second: the user sees a calm "Something went wrong" screen, clicks away, and the exception that caused it never reaches a dashboard, a Slack alert, or a log anyone reads.
The part that actually needs mapping out is which errors each mechanism sees, because the two coverage areas overlap less than they look like they should:
error.tsx up the tree renders a fallback for the user, and the server-error hook independently gets the same error with full request context, because the hook was designed specifically to still fire even when a boundary successfully catches the failure.error.tsx fires. React error boundaries work client-side too, so the fallback UI still appears — but this failure never touched your server process, so the server-error hook has nothing to see. This is the real coverage gap: a client-render error is completely invisible to server-side error tracking unless you report it yourself.useEffect, or a rejected promise — neither fires. React error boundaries only catch errors during rendering; they were never designed to catch errors in event handlers or async callbacks. This is the gap most teams don't discover until a user reports "the button did nothing" and there's no error anywhere in any system.// app/dashboard/error.tsx — catches render-phase errors, client or server
"use client";
import { useEffect } from "react";
export default function Error({ error }: { error: Error & { digest?: string } }) {
useEffect(() => {
// This is the only place a purely client-side render error
// gets any chance to reach your monitoring — instrumentation's
// server hook never sees it.
reportToMonitoring(error, { source: "error-boundary" });
}, [error]);
return <p>Something went wrong loading the dashboard.</p>;
}
// app/dashboard/RevenueChart.tsx — a click handler, not a render
"use client";
export function RevenueChart() {
return (
<button
onClick={async () => {
try {
await exportChart();
} catch (err) {
// Neither error.tsx nor the server-error hook will ever see this.
// It's on you to catch it and report it, right here.
reportToMonitoring(err, { source: "export-click" });
}
}}
>
Export
</button>
);
}
The error.tsx fallback only exists because React re-threw a render-phase error into the nearest boundary — that's the one place client-side reporting is possible without extra wiring. The click handler is a completely different situation: nothing above it is watching, so the try/catch and manual report are the entire error-tracking story for that code path.
error.tsx picks up automatically for a Server Action specifically."Your team has error boundaries everywhere and users still report bugs support can't find in the logs" is a scenario that tests whether a candidate can name the actual gap — not "add more error boundaries," but recognizing that render-phase boundaries and server-side instrumentation each cover a slice of the failure surface, and event handlers and async callbacks need their own explicit reporting because nothing else is watching them.