Next.js 'Client-Side Exception Has Occurred': How to Fix

Your Next.js app shows a white page that says 'Application error: a client-side exception has occurred'. Find the real error behind it, match it to a cause, and add a fallback so users never see it again.

"Application error: a client-side exception has occurred" means JavaScript threw an error in the browser and nothing in your React tree caught it. The message hides the real error on purpose. Open the browser console, read the first red error, match it to one of six common causes (browser-only code, missing data, hydration mismatch, stale chunks, a bundler bug, or a browser extension), and add an error.tsx so users see a fallback instead of a blank page.

This page is the production fallback that Next.js shows when a client component crashes. In development you get a detailed overlay. In production you get one generic line. The Next.js error page for this message says the same thing: a client-side error happened in production, it was not caught by an error boundary, and more detail should be in the browser console. We could not open nextjs.org from our writing environment, so that summary comes from search results. Check the page for the current wording.

The steps below go in the order that finds the cause fastest.

Step 1: Read the Real Error in the Console

Open the failing page, open DevTools, and look at the Console tab. Reload once with the console open so you do not miss the first error. The first red line matters most. Later errors are often side effects.

Write down three things:

  • The error message, such as TypeError: Cannot read properties of undefined (reading 'map').
  • The file and line, even if it is minified like chunks/724-abc.js.
  • Whether it happens on every load, or only for some users or after some time.

If the stack is minified, set up source maps so the trace points at your own files. If the console is empty, jump to Step 6. That usually means a stale tab or an extension.

Step 2: Reproduce It With a Production Build

next dev is more forgiving than production. It does not minify, and it shows overlays. Run the same build your users get:

npm run build && npm run start

Many reports on GitHub follow this pattern. One Next.js issue describes the error appearing only after next build and next start, never in next dev. A Vercel community thread describes a route that crashed only on the deployed site with a redacted message. If it reproduces locally, you can debug it with a normal stack trace. If it does not, compare environment variables and the data your production database returns.

Step 3: Check for Browser-Only Code During Render

Client components still run once on the server for the first HTML. Code that touches window, document, localStorage or navigator while rendering can fail there, or return a different result than in the browser.

'use client'

// Crashes during server render
const theme = localStorage.getItem('theme')

// Safe: read it after mount
import { useEffect, useState } from 'react'

export function ThemeLabel() {
  const [theme, setTheme] = useState<string | null>(null)
  useEffect(() => {
    setTheme(localStorage.getItem('theme'))
  }, [])
  return <span>{theme ?? 'light'}</span>
}

Move browser reads into useEffect. For a whole component that cannot run on the server, load it with next/dynamic and ssr: false.

Step 4: Check for Missing or Unexpected Data

This is the most common cause in real apps. A component expects an array or an object, and production returns null, an empty response, or a different shape. Local data is clean. Real data is not.

// Crashes if items is undefined
return items.map((item) => <Row key={item.id} item={item} />)

// Defensive
return (items ?? []).map((item) => <Row key={item.id} item={item} />)

Also check environment variables. A NEXT_PUBLIC_ variable that is missing in your hosting dashboard is undefined in the browser bundle, and the code that uses it can throw. Our guide on environment variables undefined in production covers why. For the exact error text, see TypeError: Cannot read properties of undefined.

Step 5: Look for a Hydration Mismatch

If the server HTML differs from what React renders on the client first, hydration can fail. Typical triggers are Date.now(), Math.random(), time zones, and values that differ between server and browser. The console message usually mentions hydration, text content, or "did not match". The full fix is in Next.js hydration mismatch error: causes and fix.

Step 6: Rule Out Stale Tabs, Stale Chunks and Extensions

Some crashes depend on the user, not your code.

  • Deploys while a tab is open. The old page asks for JavaScript files that no longer exist. That ends in a chunk load failure. Read ChunkLoadError after deploy.
  • Tabs left open for hours. One GitHub issue reports this error on Safari mobile after a tab sat in the background for over 12 hours, with nothing in the console. A reload fixed it. The issue was closed without a diagnosis, so treat it as a lead, not a known cause.
  • Extensions and translation tools. Page translators and some extensions rewrite the DOM, which can break React. Test in a private window with extensions off.

If only some users see the crash, compare their browser, device and country. Our post on errors on one browser, device or country shows how.

Step 7: Consider a Minifier or Library Bug

If the stack points into a third-party package, and the error appears only in production builds, minification may be involved. A Next.js issue reports a production-only crash with a package that worked after the reporter turned off swcMinify. That option is from older Next.js versions, so check which version you run before copying the workaround. A safer first move is to update Next.js and the package, then retest. If it still fails, find the smallest component that triggers it and report it with a minimal repro.

Step 8: Add Error Boundaries So Users Never See the White Page

You cannot prevent every bug, but you can control what users see. In the App Router, add an error.tsx next to your page or layout:

// app/error.tsx
'use client'

import { useEffect } from 'react'

export default function Error({
  error,
  reset,
}: {
  error: Error & { digest?: string }
  reset: () => void
}) {
  useEffect(() => {
    // send to your error tracker here
    console.error(error)
  }, [error])

  return (
    <div>
      <h2>Something went wrong</h2>
      <button onClick={() => reset()}>Try again</button>
    </div>
  )
}

error.tsx does not catch errors in the root layout. For that, add app/global-error.tsx, which must render its own <html> and <body>. Place boundaries per route so one broken widget does not take down the whole page. Our guide to React error boundaries in production shows the pattern in more detail.

Logging inside the boundary matters. Without it, the crash is caught and users are fine, but you never learn it happened.

How Relia Helps

This error is hard because it happens in the user's browser, and you only see it if someone tells you. Relia watches users, APIs and the database of a live product. It joins signals such as a deploy, a failing request and users hitting a browser error into one incident explained in plain words, and shows who was affected. When there is a stack trace, it gives a fix plan with the file and line, and after a deploy it checks whether the fix worked. Set up on Node.js and browser apps with npx relia-sdk@latest init. There is a Free plan, and you can try the live demo.

FAQ

What does "a client-side exception has occurred" mean in Next.js?

JavaScript threw an uncaught error in the browser in a production build. Next.js hides the details and shows a generic page. The real error is in the browser console.

Why does it only happen in production and not in development?

Production builds are minified, run without the dev overlay, and use real data and real environment variables. Test with next build and next start to reproduce it.

How do I see the actual error behind it?

Open the browser console and read the first error. Add source maps to get readable file names, and log errors from error.tsx to an error tracker.

Why does the error show up after I leave a tab open?

A new deploy can remove the files an old tab needs, or a long-idle tab can lose state. A reload fixes it. Handle it with a fallback page and a prompt to refresh.

Does error.tsx catch every client-side exception? No. It does not catch errors in the root layout, and it does not catch errors in event handlers. Use global-error.tsx for the layout, and wrap event handler code in try/catch.

All guides