skip to content

In the Next.js App Router, what does adding an error.tsx file to a route segment do, why must that file start with 'use client', and what does Next pass to it as props?

level: middleimportance: must knowfreq 62%

answer

  1. boundary generated from a filename
  2. browser has to own the retry
  3. two props, one of them retries
  4. the segment's own layout sits above it
  5. root layout needs a different file

basics

~20 s

An error.tsx file wraps its route segment's page and everything nested below it in an error boundary. It must be a client module because recovery happens in the browser, and Next renders it with two props: the error and a reset function that retries the segment.

solid answer

~50 s

Adding `error.tsx` to a segment tells Next to wrap that segment's page and all nested children in a React error boundary automatically — you never write the boundary yourself. When a render throws inside that subtree, the rest of the app stays mounted and only the failing region is replaced by your error UI. The file must begin with `'use client'` because the boundary and its retry button run in the browser. Next passes it two props: `error`, an `Error` object that also carries an optional `digest` string, and `reset`, a function that re-renders the boundary's contents so the user can retry without a full page reload. The important scoping detail is that the boundary sits *inside* the segment's own `layout.tsx`, so an error thrown by that layout is not caught here — it escapes to the parent segment's `error.tsx`, and a failure in the root layout needs `app/global-error.tsx`.

code

typescript · 17 lines
typescript
'use client'

export default function Error({
  error,
  reset,
}: {
  error: Error & { digest?: string }
  reset: () => void
}) {
  return (
    <div role="alert">
      <h2>Could not load this section.</h2>
      {error.digest ? <p>Reference: {error.digest}</p> : null}
      <button onClick={() => reset()}>Try again</button>
    </div>
  )
}

go deeper

for a junior

Know that the filename alone creates the boundary, that it needs the client directive at the top, and that the retry button calls the reset prop Next hands you.

for a middle

Explain the nesting: the boundary sits inside the segment's own layout, so that layout's errors escape upward, and root layout failures need the global error file with its own html and body. Name both props and what digest is for.

for a senior

Demonstrate placement judgment — a boundary per independently-failing region so one widget cannot blank the page — and connect digest to server logs so a production report is actually traceable.

for a principal

Own the failure taxonomy across the app: which failures degrade a region, which are not-found rather than errors, what the last-resort UI must never depend on, and how error reporting ties into observability.

## What the file convention buys you In plain React, catching a render error means writing a boundary component and remembering to place it around every risky subtree. The App Router turns that into a filesystem convention: drop `error.tsx` into a route segment and Next wraps that segment's page — and every segment nested beneath it — in a boundary for you. The practical effect is **partial failure instead of total failure**. If `app/dashboard/analytics/page.tsx` throws while rendering, only the analytics region is swapped for your error UI. The dashboard shell, the navigation and any sibling regions keep working, because they live above the boundary. Boundaries also **nest**. Next uses the closest `error.tsx` above the throwing component. Putting one at `app/error.tsx` gives you a catch-all; adding another at `app/dashboard/error.tsx` gives the dashboard its own tailored message and keeps its failures from reaching the app-wide one. ## Why 'use client' is mandatory `error.tsx` must start with the `'use client'` directive. Two reasons, and an interviewer usually wants at least the first: 1. **Error boundaries are a client-side capability.** The boundary needs to hold the caught error in state and re-render, which is browser work. 2. **`reset` is interactive.** The whole point of the convention is a retry affordance the user can click, and interactivity requires a client module. So even though the code that failed may have run entirely on the server, the file that *displays* the failure is always a client component. A `'use server'`-flavoured or plain server version of the file is not an option. ## The two props ```tsx 'use client' export default function Error({ error, reset, }: { error: Error & { digest?: string } reset: () => void }) { return ( <div role="alert"> <h2>Something went wrong loading this section.</h2> {error.digest ? <p>Reference: {error.digest}</p> : null} <button onClick={() => reset()}>Try again</button> </div> ) } ``` - **`error`** — an `Error` instance. Its `digest` property is an automatically generated identifier for errors that originated on the server; it is the handle you use to correlate what the user saw with what your server logs recorded. - **`reset`** — calling it re-renders the boundary's contents. If the failure was transient (a flaky upstream call), the segment recovers in place. If the cause is still there, the boundary catches again and the user sees the same UI. Note what `reset` is *not*: it is not a page reload and not a router navigation. It retries the subtree the boundary owns. ## The scoping rule that trips people up The generated boundary is placed **inside** the segment's own layout, wrapping the page. Draw it as nesting: ``` <Layout> <- app/dashboard/layout.tsx <ErrorBoundary> <- app/dashboard/error.tsx <Page /> <- app/dashboard/page.tsx </ErrorBoundary> </Layout> ``` Because the layout is *above* the boundary, an error thrown while rendering `app/dashboard/layout.tsx` cannot be caught by `app/dashboard/error.tsx`. It propagates upward to the nearest ancestor boundary — `app/error.tsx`, if it exists. Follow that chain to the top and you reach the root layout, which has no ancestor at all. That is what `app/global-error.tsx` is for: it is the boundary of last resort, it replaces the entire application shell rather than a region, and because the root layout has been taken out of the picture, the file must render its own `<html>` and `<body>` elements. Keep it deliberately plain — it cannot depend on anything the root layout provided. In development you will often see Next's development error overlay on top of it, so verify the real appearance in a production build. ## What error.tsx does not cover A render boundary catches errors thrown while rendering the subtree it owns. Failures raised outside a render pass — for example inside an event handler in a client component — are not render errors, so they never reach it; those you handle with ordinary `try/catch` and your own state. Also keep "no data" separate from "broken". A missing record is a not-found case with its own convention and its own 404 status, not an application error. Routing a missing record into `error.tsx` produces a misleading UI and the wrong status code. ## How to place them in a real app A sensible default is one boundary near the root as a safety net, plus a boundary around each region that fetches independently and can plausibly fail on its own. Too few boundaries and one flaky widget takes down the page; too many and you scatter half-broken regions with no coherent story. Give each one a message that says what failed and offers `reset`, and surface `digest` so support can find the matching server log.

  • Where does an error thrown by app/dashboard/layout.tsx get caught?
    Not by `app/dashboard/error.tsx` — that boundary is nested inside the layout it would need to catch. The error propagates to the nearest ancestor boundary, typically `app/error.tsx`. If you specifically want to guard a layout's own rendering, the boundary has to live in a parent segment.
  • What is app/global-error.tsx for, and why must it render its own html and body?
    It is the last-resort boundary that catches failures in the root layout itself. Because it replaces the root layout rather than rendering inside it, nothing else emits the document shell, so the file must render `<html>` and `<body>` itself. Keep it minimal — it cannot rely on anything the root layout set up.
  • A user clicks the retry button and sees the same error again. Is reset broken?
    No. `reset` re-renders the boundary's contents; it does not fix the underlying cause. If the failure is deterministic — a bad query, a permanently missing dependency — the re-render throws again and the boundary catches again. It only helps with transient failures such as a flaky upstream call.
  • Should a missing database record be surfaced through error.tsx?
    No. A missing record is a not-found case, which has its own file convention and produces a 404 status. Sending it to `error.tsx` reports a broken application for what is actually a valid outcome, and returns the wrong status code to crawlers and monitoring.

saying these in an interview costs you the question

  • Says error.tsx can be a server component
  • Thinks reset re-runs the failed request or reloads the page
  • Expects error.tsx to catch its own segment's layout errors
  • Believes one root error.tsx is enough for the whole app
  • Uses error.tsx for missing records instead of the not-found convention

context