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.
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.