22 августа 2026 г. · 2 мин чтения
Одна строка в макете сделала сайт втрое медленнее
Сайт отвечал около 880 мс. Первая версия объяснения была географической: сервер далеко, пакеты идут долго. Переезд поближе дал 679 мс — заметно меньше, но совсем не то, чего мы ждали.
Значит, дело не в расстоянии. Дальше пришлось смотреть, что именно происходит на каждый запрос.
Общий макет сайта спрашивал сессию: кто вошёл, чтобы шапка показала имя. Одна строка, выглядит безобидно. Но чтение сессии — это чтение куки, а чтение куки делает страницу динамической: Next перестаёт считать её статической, перестаёт отдавать из кэша и собирает заново на каждый запрос. Макет обслуживает все страницы, поэтому под это правило попал весь сайт целиком.
Решение — не спрашивать сессию на сервере. Шапка узнаёт, кто вошёл, уже в браузере, отдельным запросом. Страницы возвращаются в кэш и раздаются с ближайшего к посетителю узла.
Ответ упал до примерно 300 мс.
Плата за это есть, и её видно: пока браузер выясняет, кто вошёл, вместо кнопки входа держится пустое место такой же ширины. Без этого шапка дёргалась бы при каждой загрузке.
Урок, который стоит запомнить: в Next любое обращение к куке, заголовку или к тому, что зависит от запроса, стоит не миллисекунды на вызов, а отказ от кэширования всей ветки страниц под этим макетом. Такие вызовы дороже, чем выглядят, и дороже всего — в общих макетах.
С тех пор на этом сайте есть отдельное правило: в макете не читать ничего, что зависит от запроса. Красную полосу «вы вошли под чужим аккаунтом» пришлось делать клиентским компонентом с отдельным запросом именно поэтому — иначе она вернула бы 880 мс обратно.
export default async function LocaleLayout({ children, params }) { const { locale } = await params; /** * Сессию здесь НЕ спрашиваем. * * auth() читает куку, а это делает страницу динамической: Next * пересобирает её на каждый запрос и запрещает кэширование на * пограничных узлах. Из-за одной строки весь сайт ехал целиком * при каждом открытии. * * Теперь сессию получает шапка на стороне браузера — см. * SessionBoundary. Страница остаётся статической. */ const settings = await getSettings(); return <SessionBoundary>{children}</SessionBoundary>;}