Как делать12 октября 2026 г.12 мин чтения
Справочник разработчикаШаг 7 из 8

Частые ошибки JavaScript, React и Next.js и как их исправить

Коротко

Справочник из 33 частых ошибок фронтенда и Node.js: для каждой — точный текст сообщения, полученный при воспроизведении в Node.js 24, React 19.3, Vite 8 и Next.js 16.4, причина, исправление и как не допускать её снова. Ищите по первой строке ошибки: тип (TypeError, SyntaxError) и текст после двоеточия подсказывают раздел. Большую часть ошибок React и TypeScript ловят линтер и tsc ещё до запуска.

Разбор ошибокУровень: НачальныйЧастые ошибки

После материала вы сможете

  • по тексту сообщения найти причину и исправление для 33 частых ошибок JavaScript, TypeScript, React, Next.js, Node.js и npm
  • отличить ошибку данных (undefined, HTML вместо JSON) от ошибки окружения (ECONNREFUSED, EADDRINUSE, CORS)
  • исправить ошибки гидратации и границы Server/Client Components в Next.js App Router
  • настроить линтер и tsc, чтобы часть ошибок ловилась до запуска

Проверено на

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

Сверено с документацией: 12 октября 2026 г.

Содержание
  1. 01JavaScript: TypeError и ReferenceError
  2. 02Асинхронность и сеть: await, промисы, fetch, JSON, CORS
  3. 03Node.js и npm: модули, зависимости, порты
  4. 04TypeScript: TS2322, TS2339, TS2345, TS18048, TS7006, TS2307
  5. 05React: повторные рендеры, ключи и правила хуков
  6. 06Next.js: гидратация, Server и Client Components
  7. 07Как искать ответ самостоятельно

JavaScript: TypeError и ReferenceError

Все сообщения ниже — реальный вывод: каждую ошибку мы воспроизвели во временных проектах, браузерные — в Chrome 154. Длинные стеки сокращены до первой строки с вашим кодом; как читать стек целиком, разобрано в статье об отладке и stack trace. Тексты Chrome и Node.js совпадают, потому что у них общий движок V8; в Firefox и Safari формулировки другие.

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, стек сокращён.
  • Причина — обращение к свойству у значения undefined: здесь у user нет address. Чаще всего данные ещё не загрузились, API вернул другую форму или в имени поля опечатка. В скобках — имя свойства, которое пытались прочитать, поэтому искать надо то, что слева от него.
  • Исправление — проверить значение перед обращением: опциональная цепочка ?. и значение по умолчанию ??.
  • Как не допускать — описывать форму данных типами TypeScript и проверять ответ API на границе, а не в глубине компонентов.
01-fix.mjs
const user = { name: "Aigerim" };
console.log(user.address?.city ?? "city not set");
Исправление: вывод city not set вместо исключения.
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, стек сокращён.
  • Причина — вызывают как функцию то, что ею не является. Классика — data.map, когда API вернул объект { items: [...] }, а не массив. То же бывает при опечатке в имени метода и при путанице import x с import { x }.
  • Исправление — вывести значение и его тип (console.log(data), Array.isArray(data)) и обращаться к нужному полю.
  • Как не допускать — типизировать ответ API; TypeScript сообщит Property 'map' does not exist ещё до запуска.
02-fix.mjs
const data = { items: [1, 2, 3] };
const list = Array.isArray(data) ? data : data.items;
console.log(list.map((x) => x * 2));
Исправление: вывод [ 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 — опечатка в имени и обращение к let до объявления.
  • Причина — is not defined — переменной с таким именем нет в области видимости (опечатка, не импортировали, переменная объявлена в другой функции). Cannot access … before initialization — переменная let/const существует, но строка с её объявлением ещё не выполнена.
  • Исправление — исправить имя или импорт; объявление перенести выше первого использования.
  • Как не допускать — редактор и TypeScript подчёркивают необъявленные имена; правило линтера no-use-before-define ловит второй случай.

Асинхронность и сеть: await, промисы, 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 вне async-функции — файл CommonJS, Node.js 24.
  • Причина — await написан в обычной функции. Без async он разрешён только на верхнем уровне ES-модуля.
  • Исправление — пометить функцию async, а у вызывающего кода тоже дождаться результата через await или .then().
  • Как не допускать — писать асинхронные функции сразу как async function; ошибка синтаксическая, её показывает редактор.
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. Необработанный отклонённый промис — Node.js 24 завершает процесс с кодом 1.
  • Причина — промис отклонён, и никто не подписался на ошибку: нет await внутри try/catch и нет .catch(). Если промис отклонён объектом Error, Node.js печатает саму ошибку и стек, но процесс тоже завершается.
  • Исправление — ждать каждый промис и обрабатывать ошибку там, где понятно, что с ней делать.
  • Как не допускать — не вызывать асинхронные функции «в никуда»; правило @typescript-eslint/no-floating-promises находит такие вызовы. Подробнее — в статье об обработке ошибок.
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);
}
Исправление: вывод Could not load user: User not found, процесс не падает.
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() получил HTML, Node.js 24. В Chrome текст тот же.
  • Причина — сервер ответил HTML-страницей (404, 500, страница входа), а код вызвал res.json(). Символ < — начало <!DOCTYPE html>. Обычно неверен адрес, нет такого маршрута API или сервер упал.
  • Исправление — проверять res.ok и Content-Type до разбора тела; адрес и ответ смотреть во вкладке Network.
  • Как не допускать — одна обёртка над fetch для всего проекта с проверкой статуса и понятной ошибкой.
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();
}
Исправление: вместо SyntaxError — понятное сообщение 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 — на порту никто не слушает, Node.js 24, фрагмент.
  • Причина — соединение отклонено: сервер API не запущен, запущен на другом порту или в Docker слушает только внутри контейнера. Суть всегда в cause, а не в fetch failed.
  • Исправление — запустить сервер и сверить адрес и порт; в коде логировать err.cause.code.
  • Как не допускать — адрес API — в переменных окружения, а не в коде; ожидать сервисы при старте (например, depends_on с проверкой здоровья в Docker Compose).
07-fix.mjs
try {
  await fetch("http://localhost:3999/api/products");
} catch (err) {
  console.error(err.message, "->", err.cause?.code);
}
Как достать причину: вывод 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: POST с JSON (с preflight), простой GET и то, что видит код в catch.
  • Причина — страница с одного origin (схема, домен, порт) запрашивает API с другого, а API не прислал заголовок Access-Control-Allow-Origin. Запрос с Content-Type: application/json браузер сначала проверяет предварительным запросом OPTIONS (preflight). Код получает только TypeError: Failed to fetch — подробности браузер показывает лишь в консоли.
  • Исправление — настроить заголовки на сервере API: разрешить конкретный origin, а для preflight ответить на OPTIONS с методами и заголовками. Во фронтенде CORS не чинится — mode: 'no-cors' лишь делает ответ нечитаемым.
  • Как не допускать — список разрешённых origin в конфигурации API; * не подходит для запросов с куками. В Next.js и Vite можно ходить к своему API через тот же origin (Route Handler или прокси dev-сервера).
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}`));
Исправление на сервере API (фрагмент servers.mjs, Node.js http): после него страница получила [{"id":1,…}].

Node.js и npm: модули, зависимости, порты

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. Смешение ES-модулей и CommonJS — Node.js 24, фрагменты.
  • Причина — import в файле, который Node.js считает CommonJS (.cjs или "type": "commonjs"), или require в ES-модуле (.mjs или "type": "module").
  • Исправление — выбрать одну систему: в новом проекте — "type": "module" в package.json и import везде. Если в ES-модуле нужен require, его создают через createRequire из node:module.
  • Как не допускать — всегда указывать поле type. Без него Node.js 24 сам распознаёт синтаксис import, но печатает предупреждение MODULE_TYPELESS_PACKAGE_JSON и тратит время на повторный разбор.
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 — пакет не установлен и импорт без расширения, Node.js 24.
  • Причина — Cannot find package — пакета нет в node_modules: забыли npm install после git pull или установили в другую папку. Cannot find module … Did you mean — в ES-модулях Node.js относительный импорт пишется с расширением файла.
  • Исправление — npm install имя-пакета или npm install; в импорте ./utils.js вместо ./utils.
  • Как не допускать — коммитить package-lock.json и ставить зависимости через npm ci в CI; для сборщиков (Vite, Next.js) расширения не обязательны — правило касается запуска в 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, фрагмент.
  • Причина — конфликт peer-зависимостей: react-dom@19.3.0 требует react@^19.3.0, а в проекте react@18. npm 7+ отказывается строить дерево, которое нарушает это требование.
  • Исправление — привести версии к совместимым: здесь npm install react@19 react-dom@19 прошёл без ошибок. Если конфликт в чужом пакете — обновить его или дождаться версии с поддержкой нужного React.
  • Как не допускать — не лечить флагами --force и --legacy-peer-deps: документация npm прямо их не рекомендует, они ставят непроверенное сочетание. Обновлять связанные пакеты вместе.
terminal
Error: listen EADDRINUSE: address already in use :::3001
12. EADDRINUSE — порт занят, Node.js 24.
  • Причина — порт уже слушает другой процесс: второй экземпляр dev-сервера, «зависший» прошлый запуск или другое приложение.
  • Исправление — остановить старый процесс или запустить на другом порту (next dev -p 3001, vite --port 5174). Кто занял порт в Windows: netstat -ano | findstr :3000, номер процесса — в последнем столбце.
  • Как не допускать — брать порт из переменной PORT и закрывать dev-серверы, а не сворачивать терминал.
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.
  • Причина — в package.json нет скрипта с таким именем: в проекте start или serve вместо dev, либо команда запущена не в той папке.
  • Исправление — посмотреть список командой npm run и запустить нужный скрипт из папки с package.json.
  • Как не допускать — описать команды запуска в 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. Реальный вывод tsc 7.0.2 со strict: true для одного файла с шестью ошибками.

TS2322

Что значит
Значение не подходит к объявленному типу: строка "5" в переменную number
Как исправить
Привести явно (Number(...)) или исправить тип

TS2339

Что значит
У типа нет такого свойства: у Response нет data
Как исправить
Читать тело через await res.json(); для своих типов — добавить поле

TS2345

Что значит
Аргумент не того типа, что параметр функции
Как исправить
Преобразовать значение до вызова; не ослаблять тип параметра до any

TS18048

Что значит
Значение может быть undefined: find не всегда находит элемент
Как исправить
Обработать «не найдено»: if (!found), ?., ??

TS7006

Что значит
Параметр без типа при strict получил бы неявный any
Как исправить
Описать тип параметра

TS2307

Что значит
Модуль не найден по пути или для пакета нет типов
Как исправить
Проверить путь и расширение; для пакета без типов — установить @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 };
Тот же файл после исправлений: tsc --noEmit --strict завершился без ошибок.

React: повторные рендеры, ключи и правила хуков

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 в dev-режиме; ниже — та же ошибка, найденная tsc до запуска.
  • Причина — состояние меняется прямо во время рендера. Почти всегда это onClick={setCount(count + 1)}: функция вызывается при отрисовке, а не по клику.
  • Исправление — передавать функцию, а не результат её вызова: onClick={() => setCount(count + 1)}.
  • Как не допускать — TypeScript ловит этот случай как TS2322 (void вместо обработчика), линтер — правилом set-state-in-render.
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; за время теста (около 3 секунд) сообщение повторилось 247 раз.
  • Причина — эффект меняет состояние, от которого сам зависит: useEffect(() => setFilters({ ...filters, page: 1 }), [filters]) каждый раз создаёт новый объект, зависимость меняется, эффект запускается снова.
  • Исправление — перенести изменение в обработчик события, который его вызывает, или вычислять значение при рендере. Если эффект нужен — зависеть от примитива (filters.category), а не от объекта.
  • Как не допускать — не синхронизировать одно состояние с другим через эффекты — разбор в статье про useEffect.
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>
  );
}
Исправление: сброс страницы в том же событии, что и смена категории.
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. Нет key у элементов списка — Chrome 154, предупреждение только в dev-режиме: в production-сборке его нет.
  • Причина — элементы, созданные в map, не получили проп key, и React не может сопоставить их между рендерами.
  • Исправление — ставить стабильный уникальный идентификатор из данных: key={p.id}.
  • Как не допускать — не использовать индекс массива для списков, которые сортируются и меняются, и никогда — Math.random(): состояние полей внутри строк будет теряться.
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.
  • Причина — в JSX передан объект: {user} вместо {user.name}. Так же выглядит попытка вывести Date или ответ API целиком.
  • Исправление — выводить конкретные поля, даты — через toLocaleDateString(), для отладки — JSON.stringify(value).
  • Как не допускать — типизированные пропсы: TypeScript сообщает об объекте в JSX ещё в редакторе.
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. Хук после условного return — Chrome 154: предупреждение и ошибка.
  • Причина — хук вызывается не при каждом рендере: после раннего return, внутри if или цикла. React опознаёт хуки по порядку вызова, и порядок сломался.
  • Исправление — все хуки — в начале компонента, до любых условий и return; условие переносить внутрь хука или в JSX.
  • Как не допускать — правило rules-of-hooks (в eslint-plugin-react-hooks и oxlint) находит это до запуска — вывод линтера в последнем разделе.
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 — хук в обычной функции, вызванной из обработчика клика, Chrome 154.
  • Причина — хук вызван вне тела компонента или своего хука: в обработчике, обычной функции, классе. Реже — две копии React в проекте или несовместимые версии react и react-dom.
  • Исправление — вызывать хуки только на верхнем уровне компонента или функции useЧтоТо; проверить копии React командой npm ls react.
  • Как не допускать — называть свои хуки с use — линтер тогда проверяет их по тем же правилам.

Next.js: гидратация, Server и 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, дерево компонентов сокращено. Строка + — клиент, - — сервер.
  • Причина — HTML с сервера не совпал с первым рендером в браузере. Здесь — проверка typeof window при рендере: на сервере window нет, и получился другой текст. То же дают Date.now(), Math.random(), даты в локали пользователя, неверная вложенность тегов (<div> внутри <p>) и расширения браузера.
  • Исправление — первый рендер делать одинаковым, а браузерное значение получать в useEffect после гидратации. Для неизбежных различий вроде времени — suppressHydrationWarning на одном элементе.
  • Как не допускать — не читать window, localStorage и текущее время в теле компонента; для адаптивности использовать CSS-медиазапросы.
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>;
}
Исправление: ошибки нет, после гидратации на узком экране показывается 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. Хук в Server Component — next build, Next.js 16.4, Turbopack.
  • Причина — в App Router компоненты по умолчанию серверные, а useState, useEffect и обработчики событий работают только в клиентских.
  • Исправление — вынести интерактивную часть в отдельный файл с "use client" первой строкой и вставить её в серверную страницу.
  • Как не допускать — держать "use client" как можно ниже по дереву — только у интерактивных листьев. Подробнее — в статье о Server Components.
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>;
}
Исправление: next build завершился успешно, страница собрана статически.
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" не первой строкой — next build, фрагмент.
  • Причина — директива стоит после import или другого кода. Тогда это просто строка, файл остаётся серверным — поэтому следом идёт и ошибка про useState.
  • Исправление — перенести "use client" в самое начало файла, до импортов; комментарии выше допустимы.
  • Как не допускать — создавать клиентские компоненты по шаблону, где директива уже стоит первой.
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 в серверной странице, next build. Тот же текст — при передаче функции onBuy в клиентский компонент.
  • Причина — серверный компонент передаёт функцию: обработчик в JSX или проп-колбэк клиентскому компоненту. Функции нельзя сериализовать и отправить в браузер.
  • Исправление — перенести обработчик в клиентский компонент целиком; для действий с данными — Server Action (функция с "use server").
  • Как не допускать — пропсы на границе сервер → клиент только сериализуемые: строки, числа, массивы, обычные объекты.
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, клиентский компонент читает localStorage при рендере.
  • Причина — клиентские компоненты тоже рендерятся на сервере при сборке и первом запросе, а там нет window, document и localStorage.
  • Исправление — обращаться к браузерным API в useEffect или обработчиках событий; модуль, который работает только в браузере, подключать через next/dynamic с ssr: false из клиентского компонента.
  • Как не допускать — "use client" не значит «только в браузере» — помнить, что первый рендер серверный.
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>;
}
Исправление: next build прошёл успешно.
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. Серверный API в клиентском компоненте (next build) и async Client Component (next dev, консоль Chrome).
  • Причина — cookies(), headers() и асинхронные компоненты с await в теле работают только на сервере. Чаще всего "use client" добавили в файл, написанный как серверный.
  • Исправление — убрать "use client" из такого файла, а данные передать в клиентский компонент пропсами; в браузере загружать данные через useEffect или библиотеку запросов.
  • Как не допускать — разделять файлы: серверный компонент получает данные, клиентский отвечает за интерактивность.
terminal
./app/page.tsx:1:1
Error: Module not found: Can't resolve '@/components/Header'
32. Module not found — next build, фрагмент.
  • Причина — файла по этому пути нет: опечатка, другой регистр букв (header.tsx против Header.tsx) или псевдоним @/ указывает не туда.
  • Исправление — проверить путь и регистр; псевдонимы — в paths файла tsconfig.json. Строка Import map в ошибке показывает, во что раскрылся псевдоним.
  • Как не допускать — Windows и macOS по умолчанию прощают регистр, а Linux на сервере сборки — нет: имена файлов и импортов должны совпадать буква в букву.
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 с Cache Components (включены в шаблоне create-next-app 16.4).
  • Причина — серверный компонент ждёт fetch вне <Suspense>, и Next.js не может собрать статическую оболочку страницы.
  • Исправление — обернуть компонент с данными в <Suspense fallback={…}> — данные будут подгружаться потоком; если данные не нужны свежими на каждый запрос — кешировать функцию директивой "use cache".
  • Как не допускать — ставить границу <Suspense> ближе к месту загрузки данных; документация Next.js советует не отключать проверку через instant = false без необходимости.
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>
  );
}
Исправление: next build прошёл, маршрут / собран как Partial Prerender.

Как искать ответ самостоятельно

  1. Прочитайте первую строку целиком

    Тип ошибки и текст после двоеточия — главное. В Node.js над ней показаны файл, строка и ^ под местом ошибки.

  2. Найдите в стеке свой файл

    Первая строка at с путём вашего проекта — точка, где смотреть код. Строки node:internal и node_modules пропускайте.

  3. Проверьте данные

    Выведите значение перед строкой с ошибкой и его тип; для сетевых ошибок откройте вкладку Network и посмотрите статус и тело ответа.

  4. Ищите точный текст

    Без имён ваших переменных и путей, в кавычках. Официальные страницы — MDN для JavaScript, react.dev для React, nextjs.org/docs/messages для Next.js.

  5. Воспроизведите на минимальном примере

    Новый файл или проект, где ошибка повторяется в 10–20 строках, почти всегда сам показывает причину.

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.
Та же ошибка «Too many re-renders» в production-сборке Vite: текст сокращён до номера, полный — по ссылке или в dev-режиме.
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.
Линтер (oxlint 1.87 из шаблона Vite) находит ошибки 22, 24 и 25 до запуска; подсказки help обрезаны.
  • Линтер с правилами хуков React и tsc --noEmit запускаются перед коммитом и в CI
  • Ответы API проверяются на res.ok и тип содержимого до res.json()
  • Каждый промис дожидается через await или .catch()
  • Версии react, react-dom и next обновляются вместе
  • Браузерные API (window, localStorage) читаются только в эффектах и обработчиках
  • Адреса и порты сервисов — в переменных окружения
Как проверено7 · пройденосверено 12 октября 2026 г.
  • запущено, работает

    Node.js: 13 ошибок (undefined, not a function, ReferenceError ×2, JSON из HTML, await в CJS, unhandled rejection ×2, fetch failed/ECONNREFUSED, EADDRINUSE, require в ESM, import в CommonJS, ERR_MODULE_NOT_FOUND ×2, предупреждение MODULE_TYPELESS_PACKAGE_JSON) и 5 исправлений

    каждый случай — отдельный файл в node/, запуск `docker run --rm node:24.21.0-alpine node <file>` (пути /app); тексты ошибок вырезаны скриптом из файлов вывода; исправления выполнены с exit=0 и ожидаемым выводом

  • запущено, работает

    npm: ERESOLVE (react@18 + react-dom@19), Missing script "dev", исправление react@19 + react-dom@19

    node:24.21.0-alpine, npm 11.19.0, чистая папка; ERESOLVE и Missing script — exit=1, исправление — exit=0, npm ls показал react 19.3.0

  • запущено, работает

    TypeScript: TS2322, TS2339, TS2345, TS18048, TS7006, TS2307 и исправленный fixed.ts

    typescript 7.0.2 (npm latest), `tsc -p` со strict в node:24.21.0-alpine — 6 ошибок; fixed.ts — `tsc --ignoreConfig --noEmit --strict` exit=0

  • запущено, работает

    React 19.3 (Vite 8.3.4, шаблон react-ts): Too many re-renders, Maximum update depth, key, Objects are not valid as a React child, порядок хуков, Invalid hook call и 5 исправлений; Minified React error #301 в production; вывод oxlint 1.87; TS2322 для onClick={setCount(...)}

    vite dev + headless Chrome 154 через puppeteer-core 25.13 (сбор console/pageerror); исправленные варианты — без ошибок, DOM проверен; production — vite build + vite preview; oxlint src и tsc -b во временном проекте. Префикс `Error:` перед текстом исключений соответствует выводу, который Vite переслал в терминал

  • запущено, работает

    CORS: preflight-вариант, простой GET и исправление заголовками на сервере

    две страницы/два порта на node:http (4100/4101, 4102), Chrome 154 headless; после исправления страница получила JSON

  • запущено, работает

    Next.js 16.4.0 (create-next-app 16.4.0, Turbopack, Cache Components по умолчанию): useState в Server Component, "use client" не первой строкой, обработчик и функция-проп в клиентский компонент, window is not defined, next/headers в клиентском компоненте, Module not found, uncached fetch при пререндере, ошибка типов в next build; исправления use-state-fix, window-fix, uncached-fetch-fix

    отдельный `next build` на каждый случай (cases.mjs), тексты из реального вывода; исправления — build exit=0

  • запущено, работает

    Next.js dev: Hydration failed и async Client Component; исправление гидратации через useEffect

    `next dev -p 3201` + headless Chrome 154: ошибка гидратации с диффом +Mobile/-Desktop, исправленный маршрут без ошибок; сообщение об async Client Component из консоли; префикс `Uncaught Error:` — из лога next dev ([browser] Uncaught Error: Hydration failed…)

«Запущено» — код выполнен во временном проекте или контейнере, вывод в статье взят из прогона. «Частично» — проверено с заглушками вместо внешних сервисов. «Не запускалось» — нужны устройство, аккаунт или ключ; шаги сверены с официальной документацией. Файлы проверочного проекта и логи запусков сохранены в архиве InterLAB.

Расскажите о задаче — мы изучим запрос и свяжемся с вами

Есть задача?
Обсудим и оценим — бесплатно.

Расскажите о продукте или проблеме. Предложим архитектуру, срок и стоимость первой версии после изучения задачи.

  • Оценка объёма и рисков до старта
  • NDA — по вашему или нашему шаблону
  • Старт работ в течение недели

Не присылайте через форму пароли, ключи доступа и исходный код — безопасную передачу доступа обсудим отдельно.

Нажимая кнопку, вы соглашаетесь с обработкой персональных данных.