Разбор 04

Сайт сломался после обновления

Белый экран после обновления WordPress-темы, 500-я ошибка на Next.js-проекте после апдейта зависимостей, белый экран React-приложения. Три истории падения после апдейтов.

WordPress Next.js React PHP Fatal npm WP_DEBUG Hydration
Клиент
3 заказа: блог на WP, SaaS-лендинг на Next.js, SPA на React
Стек
WordPress 6.4 / PHP 8.2, Next.js 14, React 18, npm, pnpm, wp-cli
Срок
40 мин (WP) · 1.5 ч (Next.js) · 55 мин (React)
Итог
Три прода восстановлены, версии зафиксированы

Суть проблемы

Три разных клиента, одна общая беда — «обновили и всё сломалось». Кто-то нажал «Обновить» в админке WordPress, кто-то сделал npm update, кто-то обновил react-router. Результат везде один: прод лежит, пользователь видит белый экран или ошибку сервера.

Состояние продакшена До

Три лежащих сайта: WP-блог, Next.js-лендинг и React-SPA. У всех последних действий администратора — кнопка «Обновить» либо npm update. Восстановить своими силами не удалось.

После работы После

Три работающих проекта. В каждом — откат/даунгрейд несовместимой версии, точечный фикс breaking-change и зафиксированный lock-файл/версии, чтобы апдейт больше не ронял прод.

Как диагностировал

Подход единый: включить/найти логи, воспроизвести локально, прочитать ошибку, сопоставить с changelog обновлённой зависимости. Подводных камней нет — почти всегда причина лежит прямо в первой строке лога, нужно только её достать.

WordPress — белый экран = подавлённый Fatal

  1. Включил отладкуВ wp-config.php поставил WP_DEBUG = true и WP_DEBUG_LOG = true — ошибки стали писаться в wp-content/debug.log вместо глушения.
  2. Прочитал FatalПервая же строка лога: Fatal error: Uncaught Error: Call to undefined function create_function() in .../theme/functions.php on line 42. create_function() удалён в PHP 8.0 — хостинг заодно обновил PHP с 7.4 до 8.2.
  3. Проверил конструкторыТам же нашёл класс с конструктором вида function MyClass() вместо public function __construct() — deprecated в PHP 8, в сочетании с WP 6.4 даёт фатал при загрузке темы.
  4. Подтвердил версию PHPphp -v на сервере → 8.2.12. wp theme status через wp-cli показал активную кастомную тему версии, которая последний раз обновлялась под PHP 7.

Next.js — 500 после npm update

  1. Посмотрел лог рантаймаВ pm2 logs на сервере: TypeError: Cannot read properties of undefined (reading 'metadata') ... node_modules/next/dist/.... Ошибка в ядре Next.js.
  2. Сверил package.jsonВ package.json Next.js записан как "^14.0.0". Каретка допустила мажорный апгрейд — npm update поднял зависимость до несовместимой минорной сборки, где изменился контракт генерации метаданных.
  3. Воспроизвёл локальноnpm install из текущего package.json + next build → упало с тем же TypeError. Значит, проблема в коде приложения под новый контракт metadata, а не в окружении сервера.
  4. Проверил Node-версиюnode -v → 20.11.0. В .nvmrc проекта зафиксирована 18.17.0 — на сервере иная среда. Не корневая причина, но источник второго класса багов.
  5. Прочитал changelogВ changelog обновлённой минорной версии Next.js — breaking change в экспорте generateMetadata: теперь требует явного возврата Metadata-объекта, undefined падает.

React — Minified React error в проде

  1. Раскрыл source mapВ проде ошибка минифицирована: Minified React error #423. По коду #423 в доке React — это hydration mismatch: разница между серверным и клиентским HTML.
  2. Включил dev-режимЛокально в dev (npm run dev) React показал полный текст: Hydration failed because the initial UI does not match what was rendered on the server. Указание на компонент <Header />.
  3. Нашёл обращение к windowВ Header при SSR читалось window.localStorage.getItem('theme'). На сервере window не существует — тот же компонент дал разный вывод на сервере и клиенте.
  4. Сверил зависимостьВ package.json react-router-dom обновлён с 5 на 6 — как раз накануне падения. В v6 изменился API <Switch><Routes>, но это усугубляло, а не было корнем белого экрана.

Что исправил

В каждом под-кейсе — откат/даунгрейд до рабочей версии, точечный фикс несовместимости в коде и закрепление версий в lock-файле. Ниже — сниппеты до и после по всем трём проектам.

WordPress — включение отладки и фикс deprecated-конструктора

Сначала откатил активную тему на штатную, чтобы вернуть сайт онлайн, затем точечно починил кастомную тему.

wp-config.phpбыло
// отладка выключена — белый экран без подсказок
define( 'WP_DEBUG', false );
wp-config.phpстало
// ошибки пишутся в wp-content/debug.log, на экран не выводятся
define( 'WP_DEBUG', true );
define( 'WP_DEBUG_LOG', true );
define( 'WP_DEBUG_DISPLAY', false );

Откат темы через wp-cli — чтобы мгновенно вернуть сайт посетителям:

terminalстало
wp theme activate twentytwentyfour
# сайт снова грузится, чиним кастомную тему отдельно

Корневая причина — устаревший конструктор и create_function() в functions.php:

theme/functions.phpбыло
// PHP 8: create_function() удалён → Fatal error
add_filter( 'the_content', create_function(
  '$c',
  'return str_replace("old", "new", $c);'
) );

class My_Shortcode {
  // deprecated-конструктор вместо __construct
  function My_Shortcode() {
    add_shortcode( 'box', array( $this, 'render' ) );
  }
}
theme/functions.phpстало
// анонимная функция вместо удалённого create_function()
add_filter( 'the_content', function( $content ) {
  return str_replace( 'old', 'new', $content );
} );

class My_Shortcode {
  public function __construct() {
    add_shortcode( 'box', array( $this, 'render' ) );
  }
}

Next.js — фиксация версий и правка generateMetadata

Каретка в package.json пропустила мажорный апгрейд. Перешёл на точные версии и npm ci по lock-файлу.

package.jsonбыло
{
  "dependencies": {
    "next": "^14.0.0",
    "react": "^18.2.0"
  }
}
package.jsonстало
{
  "dependencies": {
    "next": "14.1.0",
    "react": "18.2.0"
  }
}
terminalстало
# установка строго по lock-файлу, без мажорных сюрпризов
npm ci
# сборка зелёная
next build

Точечный фикс breaking-change — generateMetadata должен явно возвращать объект, undefined валит рантайм:

app/layout.tsxбыло
export async function generateMetadata() {
  const data = await fetchPage();
  // если fetchPage вернёт null — вернётся undefined → TypeError
  return data?.seo;
}
app/layout.tsxстало
export async function generateMetadata(): Promise<Metadata> {
  const data = await fetchPage();
  // явный объект-пустышка вместо undefined
  return data?.seo ?? {
    title: 'Дефолтный заголовок',
    description: 'Дефолтное описание',
  };
}

React — guard на window и вынос логики в useEffect

Чтение window.localStorage при SSR давало hydration mismatch. Перенёс клиентскую логику в useEffect и добавил безопасное чтение темы.

components/Header.tsxбыло
export default function Header() {
  // window не существует на сервере → hydration mismatch
  const theme = window.localStorage.getItem('theme') ?? 'light';

  return (
    <header className={`theme-${theme}`}>
      <nav>...</nav>
    </header>
  );
}
components/Header.tsxстало
import { useState, useEffect } from 'react';

export default function Header() {
  // дефолт рендерится и на сервере, и на клиенте одинаково
  const [theme, setTheme] = useState('light');

  useEffect(() => {
    const saved = window.localStorage.getItem('theme') ?? 'light';
    setTheme(saved);
  }, []);

  return (
    <header className={`theme-${theme}`} suppressHydrationWarning>
      <nav>...</nav>
    </header>
  );
}
Перед любым апдейтом — бэкап + тестовый стенд + зафиксированный lock-файл. Апдейтить прямо на проде «на удачу» — гарантия описанных в этом кейсе падений.

Результат

Три прода подняты за один сеанс каждый. Во всех случаях хватало отката/даунгрейда и точечного фикса несовместимости — переписывать ничего не пришлось.

белый экран → работает
~40 мин
WordPress: восстановление блога и админки
500 на всех страницах → 0 ошибок
зелёный build
Next.js: фикс generateMetadata + фиксация версий
hydration: 423 → 0
0 ошибок
React: guard на window, рабочее приложение в проде
даунтайм → устранён
1 сеанс
каждый кейс — за один рабочий заход

Во всех трёх случаях рецепт совпал: откатить несовместимую версию → починить конкретный вызов, на котором падает код → закрепить точные версии в lock-файле и package.json. Повторных падений после апдейтов ни у одного из клиентов не было.

Есть похожая проблема?

Сначала найдём причину. Потом решим, что действительно нужно исправить.

Опишите симптом и что из-за него перестало работать. Я посмотрю, где искать причину и насколько задача похожа на точечный фикс.

Описать проблему ↗