Fix this error · React and Next.js
Fix: An error occurred in the Server Components render
Next.js hides the real Server Component error in production. Find it by digest, fix the usual causes, and report it with onRequestError.
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
- A data call failed. A
fetch, database query or SDK call inside anasyncServer Component threw or returned an error status that the code then treated as data. - The data was missing. The code read a property of
undefinedornull, such aspost.author.namefor a deleted author. - An environment variable is missing in production. It exists in
.env.localbut not in the hosting settings, so a client constructor throws. - A Server Action threw. An unhandled error inside a
'use server'function reaches the client the same way. - 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".orOnly 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.
next build && next startOn 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.
// 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
// 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.
// 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.
'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.
// 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.
// 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:
// 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.
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/catchin a Server Component swallows the error,onRequestErrornever sees it. Addbugtape.captureException(err)in thecatch. - Errors at build time. A page that fails while
next buildprerenders it fails the build. Nothing is running yet to report it.
The browser side is optional. With the browser SDK 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 (441) and ReactFlightServer.js for the exact production message and the serialization errors, checked 2 Nov 2026.
- Next.js: error.js file convention (
error.digest,retrystable in 16.3.0), checked 2 Nov 2026. - Next.js: instrumentation.js and
onRequestError, checked 2 Nov 2026. - Next.js: Error Handling (expected errors as return values), checked 2 Nov 2026.
- Next.js built-in global error page and error boundary callbacks, checked 2 Nov 2026.
Related errors
- ReferenceError: window is not defined:
ReferenceError: window is not defined - Next.js hydration mismatch:
Hydration failed because the server rendered HTML didn't match the client - Vercel FUNCTION_INVOCATION_FAILED:
500: INTERNAL_SERVER_ERROR Code: FUNCTION_INVOCATION_FAILED - Node.js 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:
Too many re-renders. React limits the number of renders to prevent an infinite loop.
All guides: Fix an error.
Start free. Free plan · No credit card · Set up in minutes. Or try the live demo first.