Fixing JavaScript Errors in React and Next.js
React and Next.js sites fail in their own particular ways. The console fills with numbered "Minified React error" messages, the server and browser disagree about what the page should contain, code that works on a laptop crashes during server rendering, and a single thrown error can leave visitors staring at a blank white page.
This guide is for site owners and developers whose React or Next.js site is logging errors in the JavaScript Checker or in the browser. It covers how to decode the messages, fix hydration mismatches and browser-only code, and stop one failing component taking down the whole page.
Why React errors look different
A production React build strips out its long explanatory messages to save space. Instead of a sentence,
you get something like Minified React error #418; visit https://react.dev/errors/418 for the full message.
The number is a code for the full text, and the link explains it. The development build, which you run
locally, prints the complete message, the component that caused it and a component stack showing where
it sits in the tree.
React also changes what happens after an error. In a plain script, an uncaught error stops one task and the rest of the page carries on. In React, an error thrown while rendering a component, and not caught by an error boundary, removes the entire component tree from the screen. On a site where React renders the whole page, that means a white screen, both for visitors and for any crawler that renders the page.
Frameworks such as Next.js add a third place for errors to happen: the server. Code runs once on the server to produce HTML, then again in the browser to hydrate it. An error can appear in either place, or come from the two runs disagreeing, and each needs a different fix.
Common React and Next.js errors and where they come from
| What you see | Where it happens | Usual cause |
|---|---|---|
| Hydration failed, or text content does not match server-rendered HTML (minified codes such as #418 and #423) | Browser, during hydration | Dates, random numbers, window checks,
locale formatting or invalid HTML nesting producing different markup on each side. |
window is not defined or document is not defined | Server, during rendering or build | Browser-only code at the top of a module or in the render body, or a library that touches the browser on import. |
A variable is undefined only in the browser | Browser | An environment
variable without the NEXT_PUBLIC_ prefix, which Next.js never sends to client code. |
| Blank white page with one error in the console | Browser | A render error with no error boundary above it. |
Each child in a list should have a unique "key" prop | Development only | Lists rendered without stable keys. Not shown in production, but can cause wrong items to update. |
| Hooks errors such as rendered more hooks than during the previous render | Browser or server | A hook called inside a condition, loop or early return, or two copies of React in the bundle. |
You're importing a component that needs useState | Build, Next.js App Router | A
server component using hooks or browser events without the 'use client' directive. |
Fixing hydration mismatches properly
Hydration expects the browser's first render to produce exactly the HTML the server sent. Anything that can differ between the two runs will break that. The fix is always to make the first render deterministic, then change anything browser-specific afterwards.
Dates, times and random values
A server in one time zone and a visitor in another will format the same date differently. Math.random()
and Date.now() give different answers each time. Either format dates on the server with
a fixed time zone and pass the finished string down, or render a neutral placeholder first and fill in
the local value after mounting.
'use client';
import { useEffect, useState } from 'react';
export function LocalTime({ iso }) {
const [text, setText] = useState(null);
useEffect(() => {
setText(new Date(iso).toLocaleTimeString('en-GB'));
}, [iso]);
return <time dateTime={iso}>{text ?? 'Today'}</time>;
}
Checks for the browser during render
Code such as typeof window !== 'undefined' ? A : B inside the render body produces B
on the server and A in the browser. Move the check into an effect, as above, so both sides render B first.
Invalid HTML nesting
Browsers repair some invalid HTML as they parse it. A <div> inside a <p>,
a link inside a link, or a table without its body element will be rearranged by the browser, so the structure
React finds no longer matches. The development build names the offending elements; fix the markup.
Using suppressHydrationWarning
The suppressHydrationWarning prop silences the warning for one element's own text or
attributes, one level deep. It is meant for values that are expected to differ, such as a timestamp.
It does not fix anything, and using it on a wrapper to hide wider mismatches leaves the underlying problem
in place.
Browser-only code and server rendering
Anything that uses window, document, localStorage or navigator
only exists in the browser. When that code runs during server rendering or a static build, it throws.
Three patterns cover almost every case:
- Use browser objects inside effects or event handlers. Effects never run on the server,
so
useEffectis a safe place to read storage or measure the window. - Import browser-only libraries lazily. Some map, chart and editor libraries touch
the browser as soon as they are imported. Load them with a dynamic
import()inside an effect, or withnext/dynamicandssr: falsefrom a client component. - Keep module-level code clean. A line at the top of a file such as
const width = window.innerWidthruns when the module loads, on both sides. Move it into a function that runs in the browser.
'use client';
import dynamic from 'next/dynamic';
const StoreMap = dynamic(() => import('./StoreMap'), {
ssr: false,
loading: () => <p>Loading mapโฆ</p>,
});
Environment variables that vanish in the browser
Next.js only includes environment variables in client-side code when their names start with NEXT_PUBLIC_.
Everything else stays on the server, which is deliberate: it keeps secrets such as API keys out of the
browser. A component that reads process.env.API_URL will work during server rendering and
get undefined in the browser, which often leads to a failed request to an address like undefined/products.
- Rename values the browser genuinely needs, such as a public API base address or an analytics ID,
with the
NEXT_PUBLIC_prefix. - Never add the prefix to secret keys. Call a server route or server action that uses the secret instead.
- Public values are written into the JavaScript at build time. Changing them in your hosting dashboard has no effect until you rebuild and redeploy.
- Reference each variable by its full name. Reading variables through a dynamic lookup such as
process.env[name]is not replaced at build time.
Vite-based React projects follow the same idea with a VITE_ prefix and import.meta.env;
Create React App used REACT_APP_.
The fix, step by step
Step 1
Reproduce the error in a development build
Take the errors the JavaScript Checker captured and note the page and message for each. Run the site locally with the development server, open the same page in a private window with extensions off, and reproduce the error. The development build prints the full message, the component name and a component stack, which turns a vague production code into a named file and line you can open.
Step 2
Decode minified React error codes
If you cannot reproduce it locally, open the link in the production message, which goes to the React errors page for that code and shows the full text with the values filled in. Enable production source maps or upload them to your error monitoring service so the stack points at your components. Then check the build logs and server logs, since some errors happen on the server first.
Step 3
Make server and browser render the same markup
For each hydration error, find the value that differs between the two renders.
Format dates on the server with a fixed time zone or set them after mounting, move random values and
window checks into effects, and fix invalid nesting such as a block element inside a paragraph.
Use suppressHydrationWarning only on a single element whose text is expected to differ,
such as a timestamp.
Step 4
Keep browser-only code out of server rendering
Search for window, document, localStorage and
navigator outside effects and event handlers, and move each use inside them. Load libraries
that touch the browser on import with a dynamic import or next/dynamic from a client component.
Add 'use client' only to the components that need hooks or browser events, not to whole
pages, so text, prices and links stay in the server-rendered HTML that crawlers receive.
Step 5
Expose client environment variables correctly
List every environment variable read in client components and check its name. Values
the browser needs must start with NEXT_PUBLIC_ in Next.js or VITE_ in Vite,
and must be referenced by their full name. Keep secret keys unprefixed and reach them through a server
route. Rebuild and redeploy after changing any public value, because it is written into the bundle at
build time.
Step 6
Contain failures with error boundaries
Add
an error boundary around each independent part of the page, such as reviews, recommendations, a map or
a chat widget, so one failing component shows a small fallback instead of blanking the page. In the Next.js
App Router, add error.js files to route segments and a global-error.js at the
root. Report caught errors to your monitoring service from the boundary.
Step 7
Ship and verify a production build
Deploy, then open the live site with the React Developer Tools extension installed and confirm it reports a production build rather than development. Re-run the JavaScript Checker on each affected template, check the console is clear of hydration and render errors, and compare the rendered page with the raw HTML to make sure content you moved to the client did not disappear.
Error boundaries in practice
An error boundary is a component that catches render errors in the components below it and shows a
fallback. React still requires a class component to write one yourself, though many projects use the
small react-error-boundary package instead. Next.js App Router projects get boundaries per
route segment by adding an error.js file, which must be a client component.
// app/products/[slug]/error.js
'use client';
export default function ProductError({ error, reset }) {
return (
<div role="alert">
<p>Sorry, this part of the page could not load.</p>
{/* A "Try again" button whose click handler calls reset() */}
</div>
);
}
The reset function asks Next.js to render the segment again, so wire it to a button's
click handler to give visitors a retry. Boundaries have limits worth knowing. They catch errors during
rendering, in lifecycle methods and in constructors below them. They do not catch errors in event handlers,
in asynchronous code such as a timer or a fetch callback, or in the boundary itself. Handle
those with ordinary try and catch, and set state so the next render can show
a message.
Where you place boundaries decides what visitors lose. One boundary around the whole app turns a white screen into a friendly error page, which is better but still loses everything. Smaller boundaries around optional sections, such as reviews or related products, let the main content and the buy button survive when a secondary widget fails.
Edge cases
- Two copies of React. Hook errors that make no sense often mean a package brought
its own copy of React. Run
npm ls reactand make sure only one version appears. - Errors only after client-side navigation. The first load is server-rendered but later pages render in the browser, so browser-only bugs can hide on the landing page. Test by clicking through the site, not only by loading pages directly.
- Development-only double effects. Strict Mode runs effects twice in development to expose missing clean-up. If something breaks only locally because of this, the fix is usually a proper clean-up function, not removing Strict Mode.
- Stale deployments. Visitors with an old tab open may request code chunks that a new deploy removed, causing chunk load errors. Keep previous build assets available for a while after each release.
1. What does Minified React error 418 mean?
Production React replaces its full error messages with numbered codes to save space, and each message includes a link to the React errors page that shows the full text. Code 418 is one of the hydration errors, meaning the HTML the server sent did not match what React rendered in the browser. Follow the link for the exact wording, then run the page in development mode, where React names the component and element that differed.
2. Why does my React site show a blank white page?
Since React 16, an error thrown while rendering a component that is not caught by an error boundary removes the whole component tree from the screen. If React renders the entire page, visitors and crawlers see nothing. Open the console to find the error, fix the component, and add error boundaries around independent sections so that a failure in one widget shows a small fallback message instead of emptying the page.
3. How do I fix window is not defined in Next.js?
The code is using a browser object while running on the server, during server rendering or a static build. Move any use of window, document or localStorage into a useEffect hook or an event handler, both of which only run in the browser. If a library touches the browser as soon as it is imported, load it with a dynamic import inside an effect or with next/dynamic and the ssr option switched off, from a client component.
4. Is suppressHydrationWarning a safe fix?
Only for the narrow case it was designed for: a single element whose own text or attribute is expected to differ between server and browser, such as a timestamp. It works one level deep and hides the warning without changing what renders. Using it on a wrapper to silence wider mismatches leaves the underlying problem, which can still cause React to re-render parts of the page and discard the server HTML there.
5. Why is my environment variable undefined in the browser?
Next.js only includes variables whose names start with NEXT_PUBLIC_ in client-side code, and it writes their values into the JavaScript at build time. A variable without the prefix exists on the server but is undefined in the browser. Rename values the browser genuinely needs, never secret keys, reference them by their full name, and rebuild after any change, because updating the value in your hosting dashboard alone does not alter the existing bundle.
6. Do hydration errors affect SEO?
The server-rendered HTML is still sent and can still be indexed, so a hydration warning on its own does not remove content. The risks are indirect: React may re-render the affected part of the page in the browser, which costs time and can shift layout, and if a hydration problem escalates into a render error without an error boundary, the page can go blank for any crawler that renders it. Fixing the mismatch removes both risks.
7. How can I tell if my live site is running a development build?
Install the React Developer Tools browser extension and visit the site. Its icon indicates whether the page uses a development or production build, and development builds are flagged clearly. Other signs are full, unminified React warnings in the console and much larger script files. Development builds are slower and should never be deployed, so check your build command, confirm your hosting sets the production environment, and look again after any change to the deployment pipeline.
The JavaScript Checker loads each page in headless Chromium, captures console errors and warnings, measures bundle size and execution time, and compares the rendered page with what a crawler receives.
Open JavaScript Checker Fixes โ