Список сетевых ошибок самого Chromium описывает это одним предложением:

Клиент выбрал заблокировать запрос.

Главное слово здесь: выбрал. Ничего не сломалось. Никакой сервер не был недоступен, никакой адрес не разрешился, ни один сертификат не был неправильным. Что-то, запущенное на машине посетителя, посмотрело на запрос, который собиралась делать ваша страница, и решило, что этот запрос не будет отправлен.

Это означает, что ваш сервер его не видел. В логе доступа нет строки, в трекере ошибок нет записи, и вообще ничего не найти на вашей стороне, потому что с точки зрения вашей инфраструктуры такого запроса вообще не существует.

Кто это клиент

Обычно какое-то расширение. Блокировщики рекламы и трекеров, это самый частый случай, и они установлены намного более широко, чем большинство команд думают, глядя на свои машины.

Но категория шире. Корпоративное программное обеспечение для рабочих станций фильтрует запросы ещё до того, как они покидают машину. То же самое делают некоторые антивирусы. Настройка корпоративного прокси может заблокировать хост. Параметры браузера на управляемом устройстве могут заблокировать целую категорию. Все они производят одну и ту же ошибку, и ни один из них не является чем-то, о чём знает человек, который об этом сообщает.

Что они все имеют общего, так это то, что они стоят между вашим кодом и сетью, внутри окружения пользователя, где у вас нет видимости и нет контроля.

Три ошибки, которые все говорят “заблокирован”

У Chromium есть несколько таких ошибок, они не взаимозаменяемы, и разница полностью в том, кто делал блокировку. Вот три, которые стоит знать, с описаниями из самого списка ошибок:

  • ERR_BLOCKED_BY_CLIENT: “Клиент выбрал заблокировать запрос.” Что-то на машине пользователя. Не ваше и не чужого сайта.
  • ERR_BLOCKED_BY_CSP: “Запрос был заблокирован политикой Content Security Policy.” Ваша собственная политика или та, которую добавил прокси на пути.
  • ERR_BLOCKED_BY_RESPONSE: ответ пришёл с требованиями, которые не были выполнены, такими как X-Frame-Options, проверки предков Content-Security-Policy или Cross-Origin-Resource-Policy. Это решение другой стороны.

Три ошибки, три хозяина: машина пользователя, ваша политика, их заголовки. Только посредняя исправима для вас, и это та, к которой люди обращаются в последнюю очередь. Ещё одно семейство путают с этими достаточно часто, чтобы его тоже разделить: ошибка CORS вообще не является блокировкой, это ответ, который браузер получил и потом отказался отдать вашему коду.

Что видит ваш JavaScript, и это почти ничего

Вот то, что делает это дорогим. Заблокированный запрос не приходит в ваш код как “заблокированный”.

fetch() отклоняется с TypeError, и MDN явно говорит, что это то, что вы получаете для ошибки сети в целом, “например, потому что устройство не имеет подключения”. Тот же отказ охватывает неправильно составленный URL, действительно автономное устройство и запрос, который расширение отказалось отправлять. Promise не несёт причину, поэтому в отказе нет ничего, чтобы их различить.

Стоит помнить, что не отклоняется, пока вы здесь: статус ошибки HTTP не отклоняется. 404 или 504 разрешаются нормально и оставляют вас проверять response.ok. Итак, отказ, который выдаёт минимум информации в вашем коде, это тот, где запрос вообще не покидал машину, а отказы, которые выдают максимум информации, это те, которые обработал ваш сервер.

Если вам нужно различие, браузер его имеет. Панель сети показывает запрос с сетевой ошибкой рядом с ним, и chrome://net-export её захватывает. Оба находятся на компьютере пользователя, что является проблемой, о которой говорит вся эта статья.

Почему это так трудно воспроизвести

Вы не можете это воспроизвести по своей природе, потому что причина не в вашей системе. Она в конфигурации, которая существует на одной машине, выбранной кем-то, кто может и не помнить, что он её выбрал, и совершенно возможно, что установленной их работодателем.

Итак, цикл идёт так: пользователь сообщает, что сохранение ничего не делает, вы открываете страницу, сохранение работает, вы закрываете задачу. Пользователь не ошибается и вы тоже. Разница в списке заблокированных хостов, которые вы не видите.

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

Session Replay

Бесплатное расширение для Chrome. Один клик на странице, которая ведёт себя неправильно, захватывает скриншот, консоль и сетевой журнал и отдаёт вам ссылку для вставки в задачу.

Установить расширение

Сетевой журнал решает это, потому что отказанный запрос находится в нём с ошибкой рядом. Это превращает “сохранение ничего не делает” в “этот запрос на этот хост никогда не покидал их машину”, что является совсем другим разговором и обычно коротким.

Что с этим делать

Ожидайте это для всего, что выглядит как отслеживание. Хосты с аналитикой, метриками, телеметрией, маяком или объявлениями в имени блокируются по умолчанию распространёнными списками. То же самое для путей. Если ваш собственный отчёт об ошибке находится на таком хосте, то отчёты об этом, вы их никогда не получите.

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

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

Одним абзацем

ERR_BLOCKED_BY_CLIENT означает, что что-то на машине посетителя, обычно расширение и иногда программное обеспечение их работодателя, решило, что ваш запрос не будет отправлен. Ваш сервер его не видел, fetch сообщает об этом как о том же TypeError, который он использует для автономного устройства, и единственная запись находится на компьютере, к которому у вас нет доступа. Это не то же самое, что блокировка политики, которой вы владеете, или заголовок, который отправила другая сторона, и различие этих трёх, это большая часть работы.