Skip to content

Пакет, который ломается нарочно

server-only: четыре строки кода, одна версия за четыре года, и весь его смысл — падать. Что бывает, когда предохранитель срабатывает не там: полгода, три проекта, робот, закрывший исходную задачу за три месяца до починки, и мейнтейнер, вычеркнувший собственное удобное предложение.

v1 0 stars 0 forks 0 watchers 1 branch 0 runs Public
mikicreated via APIv1

server-only, сентябрь 2022 — сентябрь 2023.

Есть на npm пакет из четырёх строк. У него одна-единственная версия, выпущенная 3 сентября 2022 года, и весь его код такой:

js
1throw new Error(
2 "This module cannot be imported from a Client Component module. " +
3 "It should only be used from a Server Component."
4);

Он существует ровно для того, чтобы падать. Пишешь import 'server-only' в начале файла — и файл, случайно утянутый в браузерную сборку, ломает сборку, а не отдаёт пользователю ключи от базы.

Это история о том, что бывает, когда предохранитель срабатывает не там.

28 февраля 2023 — Фелипе не может запустить тест.

Felipe K. De Boni открывает jest#13967. Заголовок задачи — дословный текст ошибки:

Create any component and add the import "server-only" to mark it as server only code. Try to run any test against it and they are going to fail.

Создайте любой компонент и добавьте import "server-only", чтобы пометить код как серверный. Запустите любой тест — он упадёт.

Он честно перечисляет, что уже пробовал: testEnvironment: 'node', прагму @jest-environment node. Не помогло ни то, ни другое.

Дальше приходят те, кто ищет обход. 14 марта Tim Feeley предлагает подменить пакет заглушкой: «I was able to work around this with just mocking server-only» — обошёл, просто подменив server-only заглушкой.

1 апреля Jonathan Pollak отвечает, что у него и подмена не сработала, — и на просьбу дать пример приносит целый репозиторий. Не «у меня не работает», а работающее воспроизведение.

2 апреля 2023 — Том показывает дверь.

Отвечает Tom Mrazauskas — человек с тремя сотнями влитых правок в Jest. Он не спорит с ошибкой — он открывает package.json самого server-only:

Look at the "exports" field of package.json in the 'server-only' package: index.js file throws the error you see and empty.js is simply an empty file. To point Jest resolver to the "react-server" export, you should pass customExportConditions to test environment.

Посмотрите на поле "exports" в package.json пакета server-only: index.js бросает ту самую ошибку, а empty.js — просто пустой файл. Чтобы направить резолвер Jest на экспорт "react-server", передайте customExportConditions в окружение теста.

js
1/**
2 * @jest-environment node
3 * @jest-environment-options {"customExportConditions": ["react-server"]}
4 */

Вот и вся разгадка: у пакета два лица. По умолчанию — взрыв. По условию разрешения react-server — пустота. Next.js выставляет это условие сам, поэтому внутри Next всё работает; всё, что запускается снаружи, получает взрыв.

Тот же день — Том предлагает и сам же вычёркивает.

Через несколько минут Том пишет очевидное продолжение: а давайте добавим в server-only ещё одно условие, node, — и тогда Jest заработает у всех без всяких прагм.

А потом вычёркивает эту фразу у себя же в комментарии и приписывает: «UPDATE This can't work on React side» — ОБНОВЛЕНИЕ: со стороны React так нельзя.

Со ссылкой на ответ, который он получил в тот же день от Sebastian Markbåge — автора этого самого пакета:

It wouldn't be correct to add it to "node": "./empty.js" as that would by-pass the main purpose of that module. The goal is for it to throw by default to indicate that it doesn't work by default in existing environments that might be SSR hybrid. Basically 'react-server' is an opt-in that it's ok to run server-only code in this environment.

Добавить "node": "./empty.js" было бы неправильно: это обошло бы главное назначение модуля. Он и должен падать по умолчанию — чтобы показать, что по умолчанию он не работает в существующих окружениях, которые могут быть гибридными SSR. По сути 'react-server' — это осознанное согласие: в этом окружении серверный код запускать можно.

Это редкий кадр. Мейнтейнер сообщества предлагает удобное; автор библиотеки объясняет, почему удобное здесь опаснее неудобного; предложивший не спорит, а зачёркивает своё предложение прямо в тексте, оставив его видимым.

Дальше Том смотрит глубже и говорит, где настоящая поломка: прагма помогает, только если Jest настроен вручную. С next/jest она не спасает — там код компилирует SWC, и он режет server-only раньше, чем до дела доходит резолвер. «Seems like this is a bug in Next.js» — и ссылка на соседнюю задачу.

Март — август 2023 — очередь в соседнюю дверь.

В Next.js к тому времени уже открыты #47299 (19 марта, André Mendonça) и #47448 (23 марта, Aron Jones). В первой — сорок три комментария, и половина из них состоит из двух слов «same issue».

26 мая, red2678:

Three months later.

...rip

Три месяца спустя. ...земля пухом.

15 июня, ctsstc — самая точная жалоба во всей истории:

I was trying to write my first test in a brand new repository and it's a pretty sour taste in the mouth and frustrating when the first test you write blows up and SWC gives you no useful message to go off of.

Я писал свой первый тест в совершенно новом репозитории, и это довольно кислое послевкусие: первый же тест взрывается, а SWC не даёт ни одного полезного слова.

7 июля, red2678 — про минусы под чужими «same issue»:

Also, not sure why people are downvoting the "same" comments. What do people expect them to say?? We are consumers of this, not the maintainers.

И не понимаю, почему людям ставят минусы за «то же самое». А что им ещё писать? Мы это потребляем, а не поддерживаем.

Июль — сентябрь 2023 — три захода Дамьена.

Чинит это Damien Simonin Feugas. Не одним PR — тремя, и каждый следующий признаёт, что предыдущего мало.

7 июля#52393, «jest can not load server-only code», влит 12 июля. Через двенадцать дней Дамьен сам приходит в чужую задачу и уменьшает ценность собственной работы, отвечая на вопрос «а это поможет?»:

unfortunately, #52393 only helps with testing server only code, like the one you may have in libs. It does not help with testing RSC.

к сожалению, #52393 помогает только тестировать серверный код — вроде того, что лежит в библиотеках. Тестировать серверные компоненты он не помогает.

4 августа#53578, «another attempt», влит 16 августа.

1 сентября#54891, 54 файла, +393/−142. В разделе «заметки ревьюерам» одна строка:

Hopefully this is the last attempt, and a successful one.

Надеюсь, это последняя попытка — и удачная.

Ревьюер Jiachi Liu из Next.js задаёт ровно один вопрос по существу — «значит, под Node тесты уже не запустить?» — получает ответ со ссылкой на тест из самого репозитория и через три дня вливает: Looks good! Thanks!

4 сентября 2023Lee Robinson приходит в задачу с объявлением: «we have a fix landed on the latest canary… Appreciate your patience» — правка приехала в свежую канарейку… Спасибо за терпение.

Шесть месяцев и пять дней от задачи Фелипе.

Что тем временем случилось с самой первой задачей.

Её закрыл робот.

4 мая 2023 бот пометил jest#13967 как «протухшую»: тридцать дней без активности. 3 июня закрыл. 4 июля — заблокировал обсуждение, приписав, что трекер задач не форум поддержки.

Правка, ради которой всё затевалось, приехала 4 сентября. К этому моменту задача, с которой всё началось, была закрыта три месяца, заперта два и лежала с формулировкой «неактивна».

Здесь нет виноватых.

Соблазн назначить виноватым робота — самый лёгкий и самый бесполезный.

Себастьян прав: пакет обязан падать. Модуль, который тихо ничего не делает в неизвестном окружении, — это утечка серверного кода в браузер, то есть ровно то, ради предотвращения чего он написан. Том прав дважды: и когда показал дверь, и когда вычеркнул своё же удобное предложение. Дамьен потратил два месяца и три захода на баг, который снаружи выглядит как «почините тесты», а внутри оказался в компиляторе на Rust. Люди, писавшие «same issue» четыре месяца, — не флуд: это единственный доступный им способ сказать «нас много».

Настоящая трещина — между двумя фактами, каждый из которых верен по отдельности. Пакет договаривается с окружением через условие разрешения react-server. И почти никто из тех, кто ставит import 'server-only', об этом договоре не знает — потому что в Next.js условие выставляется само, молча, и знание не требуется ровно до того дня, когда ты выходишь из Next.

Что это значит для нас

1
Проверить, что запускается то, чем вы проверяете

Взять каждый скрипт-пробу и запустить его. Не «он же работал», а прямо сейчас, в текущем окружении.

Why: У нас `import 'server-only'` стоит в пятидесяти модулях, а в `package.json` пакета не было вовсе. Сборка зелёная — Next подменяет импорт своим. Всё вне Next падало: проба линзы доступа, на которую наш метод ревью ссылается как на образец предохранителя, не запускалась ни разу.
Check
  • Каждая проба запущена сегодня, а не «когда-то работала»
  • Запуск проб включён в проход ревью, а не в память
2
Читать поле exports, а не искать обход

Когда пакет ломается в вашем окружении — открыть его package.json и посмотреть, какие условия разрешения он объявляет. Обход (заглушка, мок, alias) прячет причину.

Why: Том Mrazauskas ответил именно так: не «замокайте», а «посмотрите на exports — вот условие react-server». Наш случай лечится тем же: `tsx --conditions=react-server` вместо подмены пакета пустышкой.
Check
  • Условие проставлено в npm-скриптах, а не в каждом запуске руками
  • Пакет не подменяется заглушкой ради тишины
3
Не делать предохранитель удобнымRecommended

Если защита срабатывает не там, соблазн — ослабить защиту. Правильный ход — научить окружение объявлять себя, а не отключать проверку.

Why: Себастьян Markbåge объяснил, почему `server-only` не добавит условие `node`: тихая пустышка в неизвестном окружении — это утечка серверного кода в браузер, ровно то, ради чего пакет написан.
Check
  • Окружение объявляет себя явно (условие, флаг, переменная)
  • Проверка не отключается «на время»
4
Не считать зелёную сборку доказательством запуска

Сборщик умеет подменять и вырезать. Проверять надо запуском в том окружении, где код живёт, — и отдельно там, где его запускают руками.

Why: Наша сборка проходила, потому что Next подставлял свой `server-only`. Отсутствие зависимости не заметил никто, потому что запускать скрипты никто не пробовал.
Check
  • Скрипты и пробы запускаются в CI или хотя бы в проходе линзы
  • Зависимости объявлены, а не подставляются сборщиком

Четыре строки кода. Одна версия за четыре года. Ни одного обновления.

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

comlink-python: проверки на отказ стучались в адрес, который сервер не защищает, — и пять месяцев скрывали настоящую ошибку подписи

v1Public 0 0
updated Sep 1, 2026

Найти в открытом проекте настоящую историю поломки, восстановить ход мысли по написанному и закончить проверкой своего кода

v10Public 0 0
updated Sep 1, 2026

Люди и проекты из разборов — по поступкам, датам и ссылкам. Как определитель птиц: не оценивает, а помогает узнать, кого встретил

v9Public 0 0
updated Sep 2, 2026

Better Auth: ограничение частоты считалось от последнего запроса, включая отклонённые, — и разблокировка не наступала никогда

v6Public 0 0
updated Sep 2, 2026

Terraform: один абзац документации, в котором «безопасно» стоит вместо «зависит». Четыре года, вопрос без ответа, ответ, написанный за полтора месяца до вопроса в соседней задаче, и переработка справочника, сделавшая формулировку ещё категоричнее.

v1Public 0 0
updated Sep 2, 2026

Как проект принимает чужие правки: что написано в правилах, что видно по переписке и кого вы там встретите

v1Public 0 0
updated Sep 2, 2026