How to buildOctober 12, 202614 min read
Developer ReferenceStep 7 of 8

Common JavaScript, React and Next.js errors and how to fix them

In short

A reference of 33 common frontend and Node.js errors: for each one, the exact message text obtained by reproducing it on Node.js 24, React 19.3, Vite 8 and Next.js 16.4, the cause, the fix and how to prevent it. Search by the first line of the error: the type (TypeError, SyntaxError) and the text after the colon point to the section. Most React and TypeScript errors are caught by the linter and tsc before the code even runs.

TroubleshootingLevel: BeginnerCommon Errors

After this you will be able to

  • Find the cause and fix for 33 common JavaScript, TypeScript, React, Next.js, Node.js and npm errors from the message text
  • Tell a data error (undefined, HTML instead of JSON) from an environment error (ECONNREFUSED, EADDRINUSE, CORS)
  • Fix hydration errors and Server/Client Component boundary errors in the Next.js App Router
  • Set up the linter and tsc so some errors are caught before running

Verified with

Node.js 24 (24.21.0, образ node:24.21.0-alpine)npm 11.19TypeScript 7.0.2React 19.3Vite 8.3Next.js 16.4 (Turbopack)Chrome 154

Checked against docs: October 12, 2026

Contents
  1. 01JavaScript: TypeError and ReferenceError
  2. 02Async and network: await, promises, fetch, JSON, CORS
  3. 03Node.js and npm: modules, dependencies, ports
  4. 04TypeScript: TS2322, TS2339, TS2345, TS18048, TS7006, TS2307
  5. 05React: re-renders, keys and the Rules of Hooks
  6. 06Next.js: hydration, Server and Client Components
  7. 07How to find the answer yourself

JavaScript: TypeError and ReferenceError

Every message below is real output: we reproduced each error in temporary projects, the browser ones in Chrome 154. Long stacks are trimmed to the first line with your code; reading a full stack is covered in the article on debugging and stack traces. Chrome and Node.js messages match because they share the V8 engine; Firefox and Safari word them differently.

terminal
file:///app/01-undefined.mjs:2
console.log(user.address.city);
                         ^

TypeError: Cannot read properties of undefined (reading 'city')
    at file:///app/01-undefined.mjs:2:26
1. Cannot read properties of undefined — Node.js 24, stack trimmed.
  • Cause — reading a property of an undefined value: here user has no address. Most often the data hasn’t loaded yet, the API returned a different shape or a field name has a typo. The parentheses show the property being read, so look at whatever is to its left.
  • Fix — check the value before access: optional chaining ?. plus a default with ??.
  • Prevention — describe the data shape with TypeScript types and validate API responses at the boundary, not deep inside components.
01-fix.mjs
const user = { name: "Aigerim" };
console.log(user.address?.city ?? "city not set");
Fix: prints city not set instead of throwing.
terminal
file:///app/02-not-a-function.mjs:2
const doubled = data.map((x) => x * 2);
                     ^

TypeError: data.map is not a function
    at file:///app/02-not-a-function.mjs:2:22
2. … is not a function — Node.js 24, stack trimmed.
  • Cause — something that is not a function is being called. The classic is data.map when the API returned an object { items: [...] } rather than an array. It also happens with a typo in a method name and when import x is mixed up with import { x }.
  • Fix — log the value and its type (console.log(data), Array.isArray(data)) and use the right field.
  • Prevention — type the API response; TypeScript reports Property 'map' does not exist before the code runs.
02-fix.mjs
const data = { items: [1, 2, 3] };
const list = Array.isArray(data) ? data : data.items;
console.log(list.map((x) => x * 2));
Fix: prints [ 2, 4, 6 ].
terminal
file:///app/03-not-defined.mjs:2
console.log(totl);
            ^

ReferenceError: totl is not defined
    at file:///app/03-not-defined.mjs:2:13

file:///app/03-tdz.mjs:1
console.log(total);
            ^

ReferenceError: Cannot access 'total' before initialization
    at file:///app/03-tdz.mjs:1:13
3. ReferenceError — a typo in a name and access to a let before its declaration.
  • Cause — is not defined — no variable with that name exists in scope (a typo, a missing import, declared in another function). Cannot access … before initialization — the let/const variable exists, but its declaration line hasn’t run yet.
  • Fix — fix the name or the import; move the declaration above the first use.
  • Prevention — the editor and TypeScript underline undeclared names; the no-use-before-define lint rule catches the second case.

Async and network: await, promises, fetch, JSON, CORS

terminal
/app/05-await.cjs:2
  const res = await fetch("http://localhost:3101/api/products");
              ^^^^^

SyntaxError: await is only valid in async functions and the top level bodies of modules
4. await outside an async function — a CommonJS file, Node.js 24.
  • Cause — await is used inside a regular function. Without async it is only allowed at the top level of an ES module.
  • Fix — mark the function async, and have the caller await the result too, with await or .then().
  • Prevention — write async functions as async function from the start; it is a syntax error, so the editor shows it.
terminal
UnhandledPromiseRejection: 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(). The promise rejected with the reason "timeout".
5. Unhandled promise rejection — Node.js 24 exits the process with code 1.
  • Cause — a promise was rejected and nobody handled it: no await inside try/catch and no .catch(). If the rejection reason is an Error, Node.js prints that error and its stack, but the process still exits.
  • Fix — await every promise and handle the error where it is clear what to do with it.
  • Prevention — don’t call async functions and drop the promise; the @typescript-eslint/no-floating-promises rule finds such calls. More in the article on error handling.
06-fix.mjs
async function loadUser() {
  throw new Error("User not found");
}
try {
  await loadUser();
} catch (err) {
  console.error("Could not load user:", err.message);
}
Fix: prints Could not load user: User not found, the process keeps running.
terminal
<anonymous_script>:1
<!DOCTYPE html><html><body><h1>Not Found</h1></body></html>
^

SyntaxError: Unexpected token '<', "<!DOCTYPE "... is not valid JSON
    at JSON.parse (<anonymous>)
6. Unexpected token '<' … is not valid JSON — res.json() received HTML, Node.js 24. Chrome shows the same text.
  • Cause — the server answered with an HTML page (404, 500, a login page) and the code called res.json(). The < is the start of <!DOCTYPE html>. Usually the URL is wrong, the API route doesn’t exist or the server crashed.
  • Fix — check res.ok and Content-Type before parsing the body; inspect the URL and response in the Network tab.
  • Prevention — one fetch wrapper for the whole project that checks the status and throws a clear error.
04-fix.mjs
import { createServer } from "node:http";

const server = createServer((req, res) => {
  res.writeHead(404, { "Content-Type": "text/html" });
  res.end("<!DOCTYPE html><html><body><h1>Not Found</h1></body></html>");
}).listen(3101);

async function getJson(url) {
  const res = await fetch(url);
  const type = res.headers.get("content-type") ?? "";
  if (!res.ok || !type.includes("application/json")) {
    throw new Error(`${url}: ${res.status} ${res.statusText}, content-type ${type}`);
  }
  return res.json();
}

try {
  await getJson("http://localhost:3101/api/products");
} catch (err) {
  console.error(err.message);
} finally {
  server.close();
}
Fix: instead of a SyntaxError, a clear message http://localhost:3101/api/products: 404 Not Found, content-type text/html.
terminal
[TypeError: fetch failed] {
  [cause]: AggregateError [ECONNREFUSED]: 
      at internalConnectMultiple (node:net:1430:18)
      at afterConnectMultiple (node:net:2099:7) {
    code: 'ECONNREFUSED',
    [errors]: [
      Error: connect ECONNREFUSED ::1:3999
7. fetch failed / ECONNREFUSED — nothing listens on the port, Node.js 24, excerpt.
  • Cause — the connection was refused: the API server isn’t running, runs on another port or, in Docker, listens only inside the container. The real reason is always in cause, not in fetch failed.
  • Fix — start the server and check the host and port; log err.cause.code in code.
  • Prevention — keep the API URL in environment variables, not in code; wait for dependencies at startup (for example depends_on with a health check in Docker Compose).
07-fix.mjs
try {
  await fetch("http://localhost:3999/api/products");
} catch (err) {
  console.error(err.message, "->", err.cause?.code);
}
Getting the reason: prints fetch failed -> ECONNREFUSED.
terminal
Access to fetch at 'http://localhost:4101/api/products' from origin 'http://localhost:4100' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource.

Access to fetch at 'http://localhost:4101/api/products' from origin 'http://localhost:4102' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.

TypeError: Failed to fetch
8. CORS — Chrome 154: a POST with JSON (with preflight), a simple GET and what the code sees in catch.
  • Cause — a page on one origin (scheme, domain, port) requests an API on another, and the API didn’t send Access-Control-Allow-Origin. For a request with Content-Type: application/json the browser first sends an OPTIONS preflight. The code only gets TypeError: Failed to fetch — the browser shows details only in the console.
  • Fix — set the headers on the API server: allow the specific origin and answer the OPTIONS preflight with methods and headers. CORS cannot be fixed in the frontend — mode: 'no-cors' only makes the response unreadable.
  • Prevention — keep the list of allowed origins in the API config; * doesn’t work for requests with cookies. In Next.js and Vite you can call your API through the same origin (a Route Handler or the dev server proxy).
servers.mjs
createServer((req, res) => {
  if (fixed) {
    res.setHeader("Access-Control-Allow-Origin", ALLOWED_ORIGIN);
    res.setHeader("Vary", "Origin");
    if (req.method === "OPTIONS") {
      // preflight: which methods and headers the page may send
      res.writeHead(204, {
        "Access-Control-Allow-Methods": "GET, POST",
        "Access-Control-Allow-Headers": "Content-Type",
        "Access-Control-Max-Age": "600",
      });
      return res.end();
    }
  }
  res.writeHead(200, { "Content-Type": "application/json" });
  res.end(JSON.stringify([{ id: 1, name: "Espresso Blend 1 kg" }]));
}).listen(4101, () => console.log(`page :4100, api :4101, fixed=${fixed}`));
Fix on the API server (excerpt of servers.mjs, Node.js http): after it the page received [{"id":1,…}].

Node.js and npm: modules, dependencies, ports

terminal
/app/cjs-pkg/index.js:1
import { readFileSync } from "node:fs";
^^^^^^

SyntaxError: Cannot use import statement outside a module

file:///app/09-require-in-esm.mjs:1
const fs = require("node:fs");
           ^

ReferenceError: require is not defined in ES module scope, you can use import instead
9. Mixing ES modules and CommonJS — Node.js 24, excerpts.
  • Cause — import in a file Node.js treats as CommonJS (.cjs or "type": "commonjs"), or require in an ES module (.mjs or "type": "module").
  • Fix — pick one system: in a new project, "type": "module" in package.json and import everywhere. If an ES module needs require, create it with createRequire from node:module.
  • Prevention — always set the type field. Without it Node.js 24 detects import syntax on its own but prints a MODULE_TYPELESS_PACKAGE_JSON warning and spends time re-parsing.
terminal
Error [ERR_MODULE_NOT_FOUND]: Cannot find package 'dayjs' imported from /app/11-missing-package.mjs

Error [ERR_MODULE_NOT_FOUND]: Cannot find module '/app/utils' imported from /app/11-no-extension.mjs
Did you mean to import "./utils.js"?
10. ERR_MODULE_NOT_FOUND — a package not installed and an import without an extension, Node.js 24.
  • Cause — Cannot find package — the package isn’t in node_modules: npm install was skipped after git pull or run in another folder. Cannot find module … Did you mean — in Node.js ES modules a relative import must include the file extension.
  • Fix — npm install package-name or npm install; ./utils.js instead of ./utils in the import.
  • Prevention — commit package-lock.json and install with npm ci in CI; bundlers (Vite, Next.js) don’t require extensions — the rule applies to running in Node.js.
terminal
npm error code ERESOLVE
npm error ERESOLVE unable to resolve dependency tree
npm error
npm error While resolving: shop-front@1.0.0
npm error Found: react@18.3.1
npm error node_modules/react
npm error   react@"18" from the root project
npm error
npm error Could not resolve dependency:
npm error peer react@"^19.3.0" from react-dom@19.3.0
npm error node_modules/react-dom
npm error   react-dom@"19" from the root project
npm error
npm error Fix the upstream dependency conflict, or retry this command with --force or --legacy-peer-deps to accept an incorrect (and potentially broken) dependency resolution.
11. ERESOLVE — npm install react@18 react-dom@19, npm 11.19, excerpt.
  • Cause — a peer dependency conflict: react-dom@19.3.0 requires react@^19.3.0, while the project has react@18. npm 7+ refuses to build a tree that breaks that requirement.
  • Fix — align the versions: here npm install react@19 react-dom@19 succeeded. If the conflict comes from a third-party package, update it or wait for a release that supports your React version.
  • Prevention — don’t fix it with --force or --legacy-peer-deps: the npm docs explicitly advise against them, they install an unverified combination. Upgrade related packages together.
terminal
Error: listen EADDRINUSE: address already in use :::3001
12. EADDRINUSE — the port is busy, Node.js 24.
  • Cause — another process already listens on the port: a second dev server instance, a stuck previous run or another application.
  • Fix — stop the old process or start on another port (next dev -p 3001, vite --port 5174). Finding the owner on Windows: netstat -ano | findstr :3000, the process id is in the last column.
  • Prevention — read the port from a PORT variable and stop dev servers instead of just closing the terminal window.
terminal
npm error Missing script: "dev"
npm error
npm error To see a list of scripts, run:
npm error   npm run
13. Missing script — npm 11.19.
  • Cause — package.json has no script with that name: the project uses start or serve instead of dev, or the command was run in the wrong folder.
  • Fix — list the scripts with npm run and run the right one from the folder that contains package.json.
  • Prevention — document the run commands in the project README.

TypeScript: TS2322, TS2339, TS2345, TS18048, TS7006, TS2307

terminal
src/errors.ts(4,7): error TS2322: Type 'string' is not assignable to type 'number'.
src/errors.ts(9,14): error TS2339: Property 'data' does not exist on type 'Response'.
src/errors.ts(17,13): error TS2345: Argument of type 'string' is not assignable to parameter of type 'number'.
src/errors.ts(22,13): error TS18048: 'found' is possibly 'undefined'.
src/errors.ts(25,23): error TS7006: Parameter 'items' implicitly has an 'any' type.
src/errors.ts(30,28): error TS2307: Cannot find module './utils/date.js' or its corresponding type declarations.
14–19. Real tsc 7.0.2 output with strict: true for one file with six errors.

TS2322

What it means
The value doesn’t match the declared type: the string "5" into a number variable
How to fix
Convert explicitly (Number(...)) or fix the type

TS2339

What it means
The type has no such property: Response has no data
How to fix
Read the body with await res.json(); for your own types, add the field

TS2345

What it means
The argument’s type doesn’t match the function parameter
How to fix
Convert the value before the call; don’t widen the parameter to any

TS18048

What it means
The value may be undefined: find doesn’t always find an element
How to fix
Handle “not found”: if (!found), ?., ??

TS7006

What it means
Under strict, an untyped parameter would get an implicit any
How to fix
Declare the parameter type

TS2307

What it means
The module isn’t found at that path or the package has no types
How to fix
Check the path and extension; for an untyped package install @types/…
fixed.ts
type User = { id: number; name: string; email?: string };

// TS2322: convert the value explicitly
const quantity: number = Number("5");

// TS2339: Response has no data — read the body with json()
async function loadUsers(): Promise<User[]> {
  const res = await fetch("/api/users");
  return (await res.json()) as User[];
}

// TS2345: convert the string before passing it
function formatPrice(price: number) {
  return `${price} ₸`;
}
const priceFromInput = "4900";
formatPrice(Number(priceFromInput));

// TS18048: handle the "not found" case
const users: User[] = [{ id: 1, name: "Aigerim" }];
const found = users.find((u) => u.id === 2);
console.log(found?.name ?? "user not found");

// TS7006: describe the parameter type
export function total(items: unknown[]) {
  return items.length;
}

export { quantity, loadUsers };
The same file after the fixes: tsc --noEmit --strict finished with no errors.

React: re-renders, keys and the Rules of Hooks

terminal
Error: Too many re-renders. React limits the number of renders to prevent an infinite loop.

src/cases/TooManyRenders.tsx(6,18): error TS2322: Type 'void' is not assignable to type 'MouseEventHandler<HTMLButtonElement> | undefined'.
20. Too many re-renders — Chrome 154, React 19.3 in dev mode; below, the same bug found by tsc before running.
  • Cause — state is set during render. Almost always it is onClick={setCount(count + 1)}: the function is called while rendering, not on click.
  • Fix — pass a function, not the result of calling it: onClick={() => setCount(count + 1)}.
  • Prevention — TypeScript catches this as TS2322 (void instead of a handler), the linter with the set-state-in-render rule.
Counter.tsx
import { useState } from "react";

export default function Counter() {
  const [count, setCount] = useState(0);
  return <button onClick={() => setCount(count + 1)}>Clicked {count} times</button>;
}
terminal
Maximum update depth exceeded. This can happen when a component calls setState inside useEffect, but useEffect either doesn't have a dependency array, or one of the dependencies changes on every render.
21. Maximum update depth exceeded — Chrome 154; during the test (about 3 seconds) the message repeated 247 times.
  • Cause — an effect updates state it depends on: useEffect(() => setFilters({ ...filters, page: 1 }), [filters]) creates a new object each time, the dependency changes and the effect runs again.
  • Fix — move the update into the event handler that causes it, or compute the value during render. If an effect is really needed, depend on a primitive (filters.category), not on the object.
  • Prevention — don’t sync one piece of state to another through effects — see the useEffect article.
Filters.tsx
import { useState } from "react";

export default function Filters() {
  const [filters, setFilters] = useState({ category: "coffee", page: 2 });
  // reset the page in the same event that changes the category
  function changeCategory(category: string) {
    setFilters({ category, page: 1 });
  }
  return (
    <p>
      <button onClick={() => changeCategory("tea")}>Tea</button> {filters.category}, page{" "}
      {filters.page}
    </p>
  );
}
Fix: reset the page in the same event that changes the category.
terminal
Each child in a list should have a unique "key" prop.

Check the render method of `ProductList`. See https://react.dev/link/warning-keys for more information.
22. Missing key on list items — Chrome 154, a dev-only warning: it’s absent in the production build.
  • Cause — elements created in map have no key prop, so React can’t match them between renders.
  • Fix — use a stable unique id from the data: key={p.id}.
  • Prevention — don’t use the array index for lists that are sorted or edited, and never Math.random(): state inside the rows gets lost.
ProductList.tsx
const products = [
  { id: 1, name: "Espresso Blend 1 kg" },
  { id: 2, name: "Colombia Supremo 250 g" },
];

export default function ProductList() {
  return (
    <ul>
      {products.map((p) => (
        <li key={p.id}>{p.name}</li>
      ))}
    </ul>
  );
}
terminal
Error: Objects are not valid as a React child (found: object with keys {name, email}). If you meant to render a collection of children, use an array instead.
23. Objects are not valid as a React child — Chrome 154.
  • Cause — an object was put in JSX: {user} instead of {user.name}. Rendering a Date or a whole API response looks the same.
  • Fix — render specific fields, dates via toLocaleDateString(), and for debugging JSON.stringify(value).
  • Prevention — typed props: TypeScript flags an object in JSX right in the editor.
Profile.tsx
const user = { name: "Aigerim", email: "aigerim@example.com" };

export default function Profile() {
  return (
    <p>
      User: {user.name} ({user.email})
    </p>
  );
}
terminal
React has detected a change in the order of Hooks called by Cart. This will lead to bugs and errors if not fixed. For more information, read the Rules of Hooks: https://react.dev/link/rules-of-hooks

   Previous render            Next render
   ------------------------------------------------------
1. useState                   useState
2. undefined                  useState
   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Error: Rendered more hooks than during the previous render.
24. A hook after a conditional return — Chrome 154: a warning and an error.
  • Cause — a hook isn’t called on every render: after an early return, inside an if or a loop. React identifies hooks by call order, and the order broke.
  • Fix — put all hooks at the top of the component, before any conditions and returns; move the condition inside the hook or into the JSX.
  • Prevention — the rules-of-hooks rule (in eslint-plugin-react-hooks and oxlint) finds this before running — the linter output is in the last section.
Cart.tsx
import { useState } from "react";

export default function Cart({ items }: { items: string[] }) {
  const [delivery, setDelivery] = useState("courier");
  const [promo, setPromo] = useState(""); // all hooks before any return
  if (items.length === 0) {
    return <p>Cart is empty</p>;
  }
  return (
    <form>
      <input value={promo} onChange={(e) => setPromo(e.target.value)} />
      <select value={delivery} onChange={(e) => setDelivery(e.target.value)}>
        <option>courier</option>
        <option>pickup</option>
      </select>
    </form>
  );
}
terminal
Error: Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for one of the following reasons:
1. You might have mismatching versions of React and the renderer (such as React DOM)
2. You might be breaking the Rules of Hooks
3. You might have more than one copy of React in the same app
See https://react.dev/link/invalid-hook-call for tips about how to debug and fix this problem.
25. Invalid hook call — a hook in a regular function called from a click handler, Chrome 154.
  • Cause — a hook was called outside a component or custom hook body: in a handler, a regular function, a class. Less often, two copies of React or mismatched react and react-dom versions.
  • Fix — call hooks only at the top level of a component or a useSomething function; check for React copies with npm ls react.
  • Prevention — name your hooks with a use prefix — the linter then checks them by the same rules.

Next.js: hydration, Server and Client Components

terminal
Uncaught Error: Hydration failed because the server rendered text didn't match the client. As a result this tree will be regenerated on the client. This can happen if a SSR-ed Client Component used:

- A server/client branch `if (typeof window !== 'undefined')`.
- Variable input such as `Date.now()` or `Math.random()` which changes each time it's called.
- Date formatting in a user's locale which doesn't match the server.
- External changing data without sending a snapshot of it along with the HTML.
- Invalid HTML tag nesting.

It can also happen if the client has a browser extension installed which messes with the HTML before React loaded.

https://react.dev/link/hydration-mismatch
  ...
                          <MenuLabel>
                            <p>
+                             Mobile menu
-                             Desktop menu
26. Hydration failed — next dev, Chrome 154, component tree trimmed. The + line is the client, - the server.
  • Cause — the server HTML didn’t match the first browser render. Here it’s a typeof window check during render: the server has no window, so the text differs. Date.now(), Math.random(), dates in the user’s locale, invalid tag nesting (<div> inside <p>) and browser extensions do the same.
  • Fix — make the first render identical and read browser-only values in useEffect after hydration. For unavoidable differences such as timestamps, use suppressHydrationWarning on one element.
  • Prevention — don’t read window, localStorage or the current time in the component body; use CSS media queries for responsiveness.
app/hydration-fix/menu-label.tsx
"use client";

import { useEffect, useState } from "react";

export default function MenuLabel() {
  const [isMobile, setIsMobile] = useState(false); // the same value on the server and on the first client render
  useEffect(() => {
    setIsMobile(window.innerWidth < 1024); // runs only in the browser, after hydration
  }, []);
  return <p>{isMobile ? "Mobile menu" : "Desktop menu"}</p>;
}
Fix: no error; after hydration a narrow screen shows Mobile menu.
terminal
./app/page.tsx:1:10
Error: You're importing a module that depends on `useState` into a React Server Component module. This API is only available in Client Components. To fix, mark the file (or its parent) with the `"use client"` directive.
    Learn more: https://nextjs.org/docs/app/api-reference/directives/use-client
27. A hook in a Server Component — next build, Next.js 16.4, Turbopack.
  • Cause — in the App Router components are Server Components by default, and useState, useEffect and event handlers only work in Client Components.
  • Fix — move the interactive part into its own file with "use client" as the first line and render it from the server page.
  • Prevention — keep "use client" as low in the tree as possible — only on interactive leaves. More in the Server Components article.
app/cart-counter.tsx
"use client";

import { useState } from "react";

export default function CartCounter() {
  const [count, setCount] = useState(0);
  return <button onClick={() => setCount(count + 1)}>Items in cart: {count}</button>;
}
Fix: next build succeeded and the page was prerendered as static.
terminal
./app/cart-counter.tsx:2:1
Error: The "use client" directive must be placed before other expressions. Move it to the top of the file to resolve this issue.
28. "use client" not on the first line — next build, excerpt.
  • Cause — the directive comes after an import or other code. It is then just a string and the file stays a Server Component — which is why the useState error follows.
  • Fix — move "use client" to the very top of the file, before the imports; comments above it are fine.
  • Prevention — create Client Components from a template that already has the directive first.
terminal
Error occurred prerendering page "/". Read more: https://nextjs.org/docs/messages/prerender-error
Error: Event handlers cannot be passed to Client Component props.
  {onClick: function onClick, children: ...}
            ^^^^^^^^^^^^^^^^
If you need interactivity, consider converting part of this to a Client Component.
29. Event handlers cannot be passed to Client Component props — onClick in a server page, next build. The same text appears when passing an onBuy function to a Client Component.
  • Cause — a Server Component passes a function: a handler in JSX or a callback prop to a Client Component. Functions can’t be serialized and sent to the browser.
  • Fix — move the handler entirely into the Client Component; for data mutations use a Server Action (a function marked "use server").
  • Prevention — props crossing the server → client boundary must be serializable: strings, numbers, arrays, plain objects.
terminal
Error occurred prerendering page "/". Read more: https://nextjs.org/docs/messages/prerender-error
ReferenceError: window is not defined
    at <unknown> (app\theme-label.tsx:4:17)
  2 |
  3 | export default function ThemeLabel() {
> 4 |   const theme = window.localStorage.getItem("theme") ?? "light";
    |                 ^
30. window is not defined — next build, a Client Component reads localStorage during render.
  • Cause — Client Components are rendered on the server too, at build time and on the first request, and there is no window, document or localStorage there.
  • Fix — access browser APIs in useEffect or event handlers; load a browser-only module with next/dynamic and ssr: false from a Client Component.
  • Prevention — "use client" doesn’t mean “browser only” — remember that the first render happens on the server.
app/theme-label.tsx
"use client";

import { useEffect, useState } from "react";

export default function ThemeLabel() {
  const [theme, setTheme] = useState("light");
  useEffect(() => {
    // the effect runs only in the browser, after hydration
    setTheme(window.localStorage.getItem("theme") ?? "light");
  }, []);
  return <p>Theme: {theme}</p>;
}
Fix: next build succeeded.
terminal
./app/user-badge.tsx:3:1
Error: You're importing a module that depends on "next/headers" into a React Client Component module. This API is only available in Server Components but one of its parents is marked with "use client", so this module is also a Client Component.
    Learn more: https://nextjs.org/docs/app/building-your-application/rendering

<Products> is an async Client Component. Only Server Components can be async at the moment. This error is often caused by accidentally adding `'use client'` to a module that was originally written for the server.
31. A server API in a Client Component (next build) and an async Client Component (next dev, Chrome console).
  • Cause — cookies(), headers() and async components that await in their body only work on the server. Most often "use client" was added to a file written as a Server Component.
  • Fix — remove "use client" from such a file and pass the data to the Client Component as props; in the browser, load data with useEffect or a data-fetching library.
  • Prevention — split files: the Server Component fetches data, the Client Component handles interactivity.
terminal
./app/page.tsx:1:1
Error: Module not found: Can't resolve '@/components/Header'
32. Module not found — next build, excerpt.
  • Cause — there is no file at that path: a typo, different letter case (header.tsx vs Header.tsx) or the @/ alias points elsewhere.
  • Fix — check the path and the case; aliases live in paths in tsconfig.json. The Import map line in the error shows what the alias resolved to.
  • Prevention — Windows and macOS ignore case by default, but Linux on the build server doesn’t: file names and imports must match exactly.
terminal
Error: Route "/": Next.js encountered uncached or runtime data during prerendering.

`fetch(...)`, `cookies()`, `headers()`, `params`, `searchParams`, or `connection()` accessed outside of `<Suspense>` prevents the route from being prerendered, blocking the page load and leading to a slower user experience.

Ways to fix this:
  - [stream] Provide a placeholder with `<Suspense fallback={...}>` around the data access
  - [cache] For uncached data (`fetch`, database calls): cache the access with `"use cache"` (does not apply to `connection()`)
  - [block] Set `export const instant = false` to allow a blocking route

Learn more: https://nextjs.org/docs/messages/blocking-prerender-dynamic
33. Uncached data during prerendering — next build with Cache Components (enabled in the create-next-app 16.4 template).
  • Cause — a Server Component awaits fetch outside <Suspense>, so Next.js can’t build the page’s static shell.
  • Fix — wrap the data component in <Suspense fallback={…}> so the data streams in; if the data needn’t be fresh on every request, cache the function with the "use cache" directive.
  • Prevention — place the <Suspense> boundary close to the data access; the Next.js docs advise against disabling the check with instant = false unless needed.
app/page.tsx
import { Suspense } from "react";

async function Products() {
  const res = await fetch("https://example.com/api/products");
  const products: { id: number; name: string }[] = await res.json();
  return <ul>{products.map((p) => <li key={p.id}>{p.name}</li>)}</ul>;
}

export default function Home() {
  return (
    <main>
      <h1>Shop</h1>
      <Suspense fallback={<p>Loading products…</p>}>
        <Products />
      </Suspense>
    </main>
  );
}
Fix: next build succeeded and / was built as a Partial Prerender.

How to find the answer yourself

  1. Read the whole first line

    The error type and the text after the colon matter most. In Node.js the file, line and a ^ under the spot are shown above it.

  2. Find your file in the stack

    The first at line with your project’s path is where to look. Skip node:internal and node_modules lines.

  3. Check the data

    Log the value and its type before the failing line; for network errors open the Network tab and check the status and response body.

  4. Search the exact text

    Without your variable names and paths, in quotes. Official pages: MDN for JavaScript, react.dev for React, nextjs.org/docs/messages for Next.js.

  5. Reproduce it minimally

    A new file or project where the error repeats in 10–20 lines almost always reveals the cause.

terminal
Error: Minified React error #301; visit https://react.dev/errors/301 for the full message or use the non-minified dev environment for full errors and additional helpful warnings.
The same “Too many re-renders” error in a Vite production build: the text is reduced to a number; the full one is at the link or in dev mode.
terminal
src/cases/InvalidHookCall.tsx:5:19: error react-hooks(rules-of-hooks): React Hook "useState" is called in function "getInitialTheme" that is neither a React function component nor a custom React Hook function. React component names must start with an uppercase letter. React Hook names must start with the word "use".
src/cases/HooksOrder.tsx:9:29: error react-hooks(rules-of-hooks): React Hook "useState" is called conditionally. React Hooks must be called in the exact same order in every component render.
src/cases/MissingKey.tsx:9:17: warning react(jsx-key): Missing "key" prop for element in iterator.
The linter (oxlint 1.87 from the Vite template) finds errors 22, 24 and 25 before running; the help hints are trimmed.
  • A linter with the React hooks rules and tsc --noEmit run before each commit and in CI
  • API responses are checked for res.ok and content type before res.json()
  • Every promise is awaited or has a .catch()
  • react, react-dom and next are upgraded together
  • Browser APIs (window, localStorage) are read only in effects and handlers
  • Service URLs and ports live in environment variables
How this was verified7 · passedchecked 12 October 2026

“Run” means the code was executed in a scratch project or container and the output in the article comes from that run. “Partial” means it was checked against stubs instead of external services. “Not run” means it needs a device, an account or a key; the steps were checked against the official documentation. The verification project files and run logs are kept in the InterLAB archive.

Tell us about your task — we'll review your request and get back to you

Got a project?
We'll scope and estimate it — for free.

Tell us about the product or the problem. After reviewing the task we'll come back with an architecture, a timeline and a price for the first version.

  • Scope and risks estimated before we start
  • NDA — yours or ours
  • Work starts within a week

Please do not send passwords, access keys or source code through this form — we will agree a safe way to share access separately.

By clicking the button you agree to the processing of your personal data.