# Fix: An error occurred in the Server Components render

A Server Component threw on the server. In production React replaces the real message with this generic one before it reaches the browser, so the browser text tells you nothing about the cause. The fix starts on the server: find the original error in your server logs by its `digest`, or report it from `onRequestError` in `instrumentation.ts`, then fix that error.

| Fact | Detail |
|---|---|
| Error | `An error occurred in the Server Components render. The specific message is omitted in production builds to avoid leaking sensitive details. A digest property is included on this error instance which may provide additional details about the nature of the error.` |
| Where you see it | Browser console, your `error.tsx` boundary, the built-in "This page couldn't load" page with `ERROR <digest>` at the bottom |
| Usual cause | A Server Component, layout or Server Action threw: a failed fetch or database call, a missing environment variable, or `undefined` data |
| Usual fix | Read the real error on the server, then handle the failure or return it as data |
| BugTape catches it | Yes, automatically on the server, with the Node SDK in `onRequestError` (Node.js runtime) |

## What the error means

In the App Router, pages and layouts are Server Components by default. They run on the server, and React streams the result to the browser. When one of them throws, React records the error on the server and sends the browser a stand-in error. In development the stand-in keeps the original message. In production React drops the message on purpose, because it can contain secrets, SQL or customer data.

The stand-in has one useful property, `digest`. It is a hash that Next.js also logs next to the original error on the server. The Next.js docs say to use it "to match the corresponding server-side logs".

So this message is never the bug. It is a pointer to a server error you have not looked at yet.

## Common causes

1. **A data call failed.** A `fetch`, database query or SDK call inside an `async` Server Component threw or returned an error status that the code then treated as data.
2. **The data was missing.** The code read a property of `undefined` or `null`, such as `post.author.name` for a deleted author.
3. **An environment variable is missing in production.** It exists in `.env.local` but not in the hosting settings, so a client constructor throws.
4. **A Server Action threw.** An unhandled error inside a `'use server'` function reaches the client the same way.
5. **A non-serializable prop crossed to a Client Component.** Passing a function or a class instance from a Server Component to a Client Component throws `Functions cannot be passed directly to Client Components unless you explicitly expose it by marking it with "use server".` or `Only plain objects can be passed to Client Components from Server Components.`, and production hides both behind the generic message.

## How to fix it

### Find the original error

Development shows the real message in the overlay. To reproduce the production case, run a production build locally and watch the terminal: the server prints the original error there.

```bash
next build && next start
```

On a host, search the function or server logs for the digest value. The line with that digest has the original message and stack.

### Handle failed data calls

Check the response before you use it, and decide what the page shows when the call fails. Call `notFound()` for a missing record so Next.js renders a 404 instead of an error.

```tsx
// app/posts/[id]/page.tsx
import { notFound } from 'next/navigation';

export default async function PostPage({ params }: { params: Promise<{ id: string }> }) {
  const { id } = await params;
  const res = await fetch(`${process.env.API_URL}/posts/${id}`);

  if (res.status === 404) notFound();
  if (!res.ok) throw new Error(`Post API answered ${res.status}`);

  const post = await res.json();
  return <h1>{post.title}</h1>;
}
```

The thrown error still shows the generic message in the browser. The difference is that the server log now says what failed.

### Guard optional data

```tsx
// Before: throws when the author was deleted
<p>{post.author.name}</p>

// After
<p>{post.author?.name ?? 'Unknown author'}</p>
```

### Fail early on missing environment variables

Read required variables once, in a server-only module, and throw a clear message. Then add the variable in your host's settings and redeploy.

```ts
// lib/env.ts
import 'server-only';

export function requireEnv(name: string): string {
  const value = process.env[name];
  if (!value) throw new Error(`Missing environment variable ${name}`);
  return value;
}
```

### Return expected errors from Server Actions

The Next.js docs recommend returning expected errors as values and reading them with `useActionState`. Throw only for errors you did not expect.

```ts
'use server';

export async function createPost(prevState: { message: string }, formData: FormData) {
  const res = await fetch(`${process.env.API_URL}/posts`, {
    method: 'POST',
    body: JSON.stringify({ title: formData.get('title') }),
  });
  if (!res.ok) return { message: 'Could not save the post. Try again.' };
  return { message: '' };
}
```

### Pass plain data to Client Components

Pass strings, numbers, dates, plain objects and arrays. Turn class instances, such as an ORM model, into plain objects first. Mark a function with `'use server'` if the client must call it.

```tsx
// Before: a class instance and a function cross the boundary
<PostCard post={postModel} onShare={() => share(postModel.id)} />

// After: plain data; PostCard calls a 'use server' action to share
<PostCard post={{ id: postModel.id, title: postModel.title }} />
```

### Show a useful fallback

Add `error.tsx` next to the page so one failed segment does not take down the whole layout. Since Next.js 16.3 the boundary receives `retry`, which re-fetches and re-renders the segment. Older versions pass `reset` instead.

```tsx
// app/posts/[id]/error.tsx
'use client';

export default function Error({ error, retry }: { error: unknown; retry: () => void }) {
  const digest = error instanceof Error && 'digest' in error ? String(error.digest) : undefined;
  return (
    <div>
      <p>This post could not load.</p>
      {digest && <p>Reference: {digest}</p>}
      <button onClick={() => retry()}>Try again</button>
    </div>
  );
}
```

## How to catch it in production with BugTape

The useful report for this error comes from the server, where the real message still exists. Next.js 15 and later call `onRequestError` in `instrumentation.ts` for errors in pages, layouts, route handlers and Server Actions. The BugTape Node SDK reports from that hook, with the route pattern (`/posts/[id]`) and the route type:

```ts
// instrumentation.ts
export async function register() {
  if (process.env.NEXT_RUNTIME === 'nodejs') (await import('@bugtape/node')).init({ service: 'web' });
}

export async function onRequestError(...args: Parameters<typeof import('@bugtape/node').captureRequestError>) {
  if (process.env.NEXT_RUNTIME === 'nodejs') await (await import('@bugtape/node')).captureRequestError(...args);
}
```

Install it with `npm install @bugtape/node` and set `BUGTAPE_KEY` in your host's environment. The report carries the error type, the redacted message and the stack frames in your code. Next.js redirect and not-found signals are skipped. `captureRequestError` waits up to 2 seconds for delivery, so the report leaves before a serverless function is frozen. Details are in the [Node.js guide](/docs/node/).

What it does not catch automatically:

- **Routes on the Edge runtime.** The checks above skip Edge, and the SDK does not support it. Move the route to the Node.js runtime or report from your own code.
- **Errors you catch yourself.** If a `try`/`catch` in a Server Component swallows the error, `onRequestError` never sees it. Add `bugtape.captureException(err)` in the `catch`.
- **Errors at build time.** A page that fails while `next build` prerenders it fails the build. Nothing is running yet to report it.

The browser side is optional. With the [browser SDK](/docs/quickstart/) installed, the generic message reaches it only when no `error.tsx` catches the error: Next.js then passes it to `reportError()`, which fires the window `error` event the SDK listens for. Your own `error.tsx` only logs to the console, which BugTape records but which does not start a report. That browser report would only carry the generic text, so the server report is the one to read.

Repeats of the same server error group into one issue with a count. Set `release` (the SDK fills it from `VERCEL_GIT_COMMIT_SHA` and other common commit variables) so that when you mark the issue fixed, a report from a newer release reopens it as a regression. Your coding agent can read the issue over MCP (`npx -y bugtape connect claude`, or `cursor` or `codex`) and propose the fix. You review the pull request.

## FAQ

### What is the digest in a Next.js error?

It is a hash that Next.js generates for the thrown error. The browser gets only the digest and the generic message. The server log has the same digest next to the original message and stack, so you can match a user's report to the real error.

### Why does it work in development but fail in production?

Often the same error happens in both. Development shows the real message and production hides it. When the error really happens only in production, the usual causes are an environment variable that is missing on the host, production data that has a `null` the test data did not, or an API that rejects the production server's requests.

### Can I show the real error message to the user?

Not for errors thrown in Server Components. Production builds remove the message on purpose. If the user should see a reason, return it as data, for example a `{ message }` value from a Server Action read with `useActionState`, or render a fallback in the page.

### Does error.tsx catch errors in layout.tsx?

Not the layout in the same folder. `error.tsx` wraps the page and nested layouts below it. An error in a layout is caught by the `error.tsx` of the parent segment, and an error in the root layout needs `app/global-error.tsx`.

## Sources

- [React error codes](https://github.com/facebook/react/blob/main/scripts/error-codes/codes.json) (441) and [ReactFlightServer.js](https://github.com/facebook/react/blob/main/packages/react-server/src/ReactFlightServer.js) for the exact production message and the serialization errors, checked 2 Nov 2026.
- [Next.js: error.js file convention](https://nextjs.org/docs/app/api-reference/file-conventions/error) (`error.digest`, `retry` stable in 16.3.0), checked 2 Nov 2026.
- [Next.js: instrumentation.js and `onRequestError`](https://nextjs.org/docs/app/api-reference/file-conventions/instrumentation), checked 2 Nov 2026.
- [Next.js: Error Handling](https://nextjs.org/docs/app/getting-started/error-handling) (expected errors as return values), checked 2 Nov 2026.
- [Next.js built-in global error page](https://github.com/vercel/next.js/blob/canary/packages/next/src/client/components/builtin/global-error.tsx) and [error boundary callbacks](https://github.com/vercel/next.js/blob/canary/packages/next/src/client/react-client-callbacks/error-boundary-callbacks.ts), checked 2 Nov 2026.

## Related errors

- [ReferenceError: window is not defined](/guides/window-is-not-defined/): `ReferenceError: window is not defined`
- [Next.js hydration mismatch](/guides/nextjs-hydration-mismatch/): `Hydration failed because the server rendered HTML didn't match the client`
- [Vercel FUNCTION_INVOCATION_FAILED](/guides/vercel-function-invocation-failed/): `500: INTERNAL_SERVER_ERROR Code: FUNCTION_INVOCATION_FAILED`
- [Node.js unhandled promise rejection](/guides/node-unhandled-promise-rejection/): `This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch()`
- [React: Too many re-renders](/guides/react-too-many-re-renders/): `Too many re-renders. React limits the number of renders to prevent an infinite loop.`

All guides: [Fix an error](/guides/).

[Start free](/console/register?utm_source=bugtape_seo&utm_campaign=error-guides). Free plan · No credit card · Set up in minutes. Or [try the live demo](/demo/?from=landing&start=1#capture) first.
