In production, a Next.js App Router app renders the segment's error.tsx but with a generic message and no details, while a separate failure replaces the entire UI with Next's default error page instead of hitting any error.tsx. Explain both behaviours and what you would change.
answer
- messages do not cross the wire
- a hash links UI to the log
- every boundary lives under the root shell
- last resort must draw its own document
- shrink what can fail at the top
basics
~20 sErrors thrown during server rendering are stripped of their details before reaching the browser and arrive with only a digest identifier, so the client-rendered error file cannot show the real message. The whole-UI replacement means the failure happened above every segment boundary — in the root layout — which only a global error file can catch.
solid answer
~50 sTwo separate mechanisms. First, an error raised on the server is not shipped to the browser verbatim: Next replaces the message with a generic one and attaches a `digest` — a generated identifier — to the `error` prop, so that server internals never leak to users. The real stack is in your server logs, keyed by that same digest, so the fix is to log server-side and render `error.digest` in the UI as a support reference rather than trying to display `error.message`. Second, each `error.tsx` boundary lives *inside* the layouts above it, so a failure while rendering the root layout is above every one of them and escapes the entire chain. The only boundary above the root layout is `app/global-error.tsx`, which replaces the whole shell and therefore has to render its own `<html>` and `<body>`. Add that file, and keep the root layout as close to inert as possible so it has little to fail on.
code
typescript · 19 lines'use client'
export default function GlobalError({
error,
reset,
}: {
error: Error & { digest?: string }
reset: () => void
}) {
return (
<html lang="en">
<body style={{ fontFamily: 'system-ui', padding: 24 }}>
<h1>The application failed to load.</h1>
{error.digest ? <p>Reference code: {error.digest}</p> : null}
<button onClick={() => reset()}>Reload</button>
</body>
</html>
)
}go deeper
Know that in production the error object you receive does not carry the real server message, and that the digest string on it is a reference code worth showing to the user.
Explain the redaction as a deliberate information-disclosure guard, and explain why root layout failures escape every segment boundary and need the global error file with its own html and body.
Connect the digest to server logging so production reports are traceable, and show the placement strategy: boundaries around independently-failing regions, plus a root layout thin enough that it rarely throws.
Own the failure architecture — what may run in the root layout at all, where configuration is validated, how error telemetry is correlated end to end, and what the organisation's last-resort screen is allowed to depend on.
## Symptom one: the message is redacted When a Server Component throws, the failure happens on the server but the UI that reports it — `error.tsx` — is a client component. Something has to cross the network, and Next deliberately does not send the original error across. The message is replaced with a generic string and the error object handed to your boundary carries a **`digest`** property: a generated identifier for that specific error. This is a security decision, not a bug. Error messages routinely embed connection strings, SQL fragments, internal hostnames and file paths. Shipping them to every visitor's browser would turn every failure into an information-disclosure incident. So the detail stays on the server and the browser gets a handle. The handle is the whole point: the same digest is emitted in the server-side log for that error. That gives you the correlation you actually need in production: ```tsx 'use client' export default function Error({ error, reset, }: { error: Error & { digest?: string } reset: () => void }) { return ( <section role="alert"> <h2>This section could not be loaded.</h2> {error.digest ? <p>Reference code: {error.digest}</p> : null} <button onClick={() => reset()}>Try again</button> </section> ) } ``` The operational consequences: - **Never build UX around `error.message` for server-thrown errors.** In development you will see the real message and in production you will not, which is exactly how this ships broken. - **Log on the server at the point of failure**, with enough request context to be useful, and make sure your log aggregation retains the digest field. - **Show the digest to the user** as a reference code. A support ticket that quotes it turns an unreproducible report into a log query. - **If a message is genuinely meant for users** — "this report has no data for the selected range" — do not throw it. Return it as data the page renders normally. Errors are for the unexpected. ## Symptom two: the whole UI is replaced The second failure never reaches an `error.tsx` at all. Recall how Next composes a segment: the generated boundary is placed inside that segment's layout, wrapping its page. Stack that up the tree and every boundary in the app sits *below* the root layout. So an error thrown while the root layout renders — a provider that reads a missing environment variable, a top-level data call, a font or theme setup that throws — is above every boundary you wrote. Nothing in the segment tree can catch it, and Next falls back to its built-in error page for the entire application. The file that covers this case is `app/global-error.tsx`. It is the boundary of last resort, and it has two properties that follow from its position: - It **replaces the root layout** rather than rendering inside it, so it must render its own `<html>` and `<body>` elements. Nothing else is left to emit the document shell. - It must be **self-sufficient**. It cannot rely on providers, theme context, fonts or data the root layout would have supplied — that is precisely what failed. Keep it plain markup and inline styling, with at most a reload affordance. One practical note when you verify it: in development Next's error overlay tends to sit on top, so check the real appearance in a production build. ## The structural fix, not just the file Adding `global-error.tsx` gives you a controlled last-resort screen, but a root layout that can fail is still a single point of failure for the whole product — one bad deploy blanks every route. The stronger move is to shrink the root layout's responsibilities: - Move data fetching out of the root layout and down into the segments that need it, where a segment boundary can contain the failure. - Validate environment configuration at startup rather than during a render, so a misconfiguration fails the deploy instead of every request. - Keep providers in the root layout free of work that can throw. Then place boundaries deliberately below it: one per independently-loaded region, so a failing widget degrades its own area while navigation and the rest of the page survive. That placement is the real senior judgment here — the two symptoms in the question are what a tree with no boundary strategy looks like under load. ## Answering it well Name the two mechanisms separately: redaction plus digest for the first, boundary position relative to the root layout for the second. Then give both a remedy — log-and-surface the digest, add the global file, and reduce what the root layout can fail at. That order shows you have operated an App Router app in production rather than only read about the conventions.
- Why does Next redact server error messages instead of forwarding them?Because the boundary that displays them runs in the browser, and error text routinely contains connection strings, SQL fragments, internal hostnames and paths. Forwarding it would leak infrastructure detail to every visitor. Next sends a generic message plus a `digest` that matches the server log entry, keeping the detail where only operators can read it.
- What must app/global-error.tsx render that an ordinary error file does not, and why?Its own `<html>` and `<body>`. It replaces the root layout rather than rendering inside it, so nothing else emits the document shell. For the same reason it must not depend on providers, fonts or data the root layout supplied — that layer is exactly what failed.
- Beyond adding the global file, how do you stop one root-layout failure from taking down every route?Reduce what the root layout does. Push data fetching down into the segments that need it so segment boundaries can contain failures, validate environment configuration at startup so misconfiguration fails the deploy rather than every render, and keep top-level providers free of work that can throw.
- A user reports an error screen. What do you ask them for?The reference code shown on the screen — the `digest`. It maps directly to the server log entry with the real stack and request context, turning an unreproducible report into a single log query. That is why rendering the digest, rather than hiding it, is worth the small UI cost.
saying these in an interview costs you the question
- Renders error.message and expects real detail in production
- Treats the digest as noise instead of a log correlation key
- Assumes a root error.tsx catches root layout failures
- Writes a global error file that depends on the root layout's providers
- Keeps heavy data fetching in the root layout with no fallback