React Error #423: There was an error during concurrent rendering
Error during concurrent rendering, switched to client rendering
Verified against React 19 source (ReactFiberWorkLoop.js, ReactDOMServer.js), React docs: concurrent-rendering, Next.js docs: error-handling · Updated April 2026
> quick_fix
A component threw during server rendering, forcing React to abandon SSR for that subtree and render it on the client only. Find the throwing component (the warning includes a stack), wrap it in an Error Boundary, and fix the underlying exception so SSR works again.
// React 19 - error boundary catches both SSR and client errors
import { ErrorBoundary } from 'react-error-boundary'
<ErrorBoundary fallback={<p>Could not render</p>}>
<ComponentThatMightThrow />
</ErrorBoundary>What causes this error
React 18+ uses concurrent rendering on the server (renderToReadableStream, renderToPipeableStream). When a component throws during the streaming render, React abandons the streamed HTML for that subtree, sends the rest of the page, and lets the client re-render the failed subtree on its own. The error is recoverable but loses the SSR benefit (slower first paint, no SEO content) and logs invariant #423 with the original throw.
How to fix it
- 01
step 1
Find the underlying error in the server logs
The console shows the original exception that fired. #423 is the wrapper - the real error has a stack pointing at your code.
- 02
step 2
Wrap the throwing component in an Error Boundary
React 19 supports Error Boundaries for both client and server. Put the boundary around the smallest reasonable subtree to limit the fallback area.
- 03
step 3
Fix the underlying exception
Common causes during SSR: reading window/document/localStorage (undefined on server), database queries failing, missing env vars, async data not awaited, schema mismatch in props.
- 04
step 4
If the component is genuinely client-only, mark it as such
Use 'use client' (Next.js App Router) or next/dynamic with ssr:false. This skips SSR entirely for that component instead of failing into #423.
// Disable SSR for a specific component import dynamic from 'next/dynamic' const Map = dynamic(() => import('./Map'), { ssr: false })
Why #423 happens at the runtime level
React's server renderer (renderToReadableStream / renderToPipeableStream) runs components inside a concurrent work loop. When a component throws synchronously, React's error path in ReactFiberThrow.js classifies the error and decides whether to retry on the client. For a recoverable subtree, React aborts the streaming HTML at that boundary, emits a placeholder for the client to fill, and logs invariant #423 with the original exception attached. The streaming bytes already sent are valid; only the failed subtree is deferred to the client. The result is a partial-SSR page where the broken section appears blank until JavaScript hydrates.
Common debug mistakes for #423
- Reading window.innerWidth at the top of a Server Component - window is undefined and the component throws during SSR, hitting #423 every render.
- Using a third-party library that calls document.querySelector in a constructor - the import alone throws on the server, no instantiation needed.
- Putting an async function as the component body in a Client Component (only Server Components support this in App Router) - the type mismatch throws and triggers #423.
- Catching the error with a try-catch around JSX - the throw happens during reconciliation, not in the JSX expression, so the catch never fires.
- Adding 'use client' to fix it without realising the component is imported by a Server Component - the SSR pass still tries to render it.
When #423 signals a deeper problem
Recurring #423 means the codebase has not enforced clean Server-Component / Client-Component boundaries. When a 'use client' directive is missing on a component that uses browser-only APIs, every render throws on the server, drops to client rendering, and silently degrades the SSR benefit. The architectural fix is to maintain a clear boundary at the import level, enforced by ESLint plugins (eslint-plugin-react-server-components) or by file-naming conventions like .client.tsx and .server.tsx. Without this, the team's mental model of what runs where drifts and the bug returns with every new feature.
Editor's take
This error tends to surface at exactly the wrong moment: during a Next.js 15 App Router migration when the team has just flipped `runtime: 'edge'` on a shared layout and pushed to production on a Friday afternoon. In a three-person startup where one engineer owns the entire frontend, the symptom is a blank hydration shell with zero visible error in the browser — because React silently falls back to client-only rendering and swallows the stack unless `NODE_ENV=development` or you're watching `renderToPipeableStream`'s `onError` callback. The on-call page comes at 2am when lighthouse scores tank and the CTO notices the HTML source is empty.
Hitting and actually fixing this error is a reliable signal that an engineer understands the React concurrent rendering model at the fiber level — not just the surface API. It's not a first-week mistake; junior devs usually encounter hydration mismatches (#418, #425) before they get here. Correctly diagnosing #423 requires reading the `onError` / `onShellError` distinction in the streaming API, understanding why `Suspense` boundaries define recovery granularity, and knowing that `use client` is a module-graph boundary, not a runtime flag. Engineers who can explain why the error disappears in dev but not in prod have internalized how React's production build strips diagnostic warnings.
In the same incident, expect to also see React Error #418 (hydration mismatch between server and client output), Error #185 (nested Suspense boundary violations), and upstream `NEXT_NOT_FOUND` uncaught throws when dynamic route segments resolve to `notFound()` inside a Server Component that lacks a boundary. If `onShellError` fires, the entire streamed response is lost — which can cascade into a 500 logged by your CDN edge layer, stripping `Cache-Control` headers and causing a full cache miss storm on the next request wave.
By Bikram Nath · Curator · Updated April 2026
Frequently asked questions
Is #423 fatal?
No - React degrades gracefully to client rendering. But you've lost the SSR benefits for that subtree (slow first paint, no crawler-readable content).
How do I find the component that threw?
Server logs show the original stack trace. Client console shows the React-side recovery warning. The original error is the actionable one.
Does React 19 add new tools for this?
Yes - the 'use' hook and improved Server Components reduce the surface area for SSR throws. But #423 still fires for legitimate bugs.