La propia lista de errores de red de Chromium lo describe en una sola frase:

El cliente decidió bloquear la petición.

La palabra que lleva el peso es decidió. Nada falló. Ningún servidor estaba caído, ningún nombre no se resolvió, ningún certificado fue incorrecto. Algo que se ejecutaba en la propia máquina del visitante miró una petición que tu página estaba a punto de hacer y decidió que no se haría.

Lo cual significa que tu servidor nunca la vio. No hay una línea en tu registro de acceso, no hay una entrada en tu rastreador de errores, y no hay nada que encontrar en tu lado en absoluto, porque desde el punto de vista de tu infraestructura la petición no existe.

Quién es el cliente

Normalmente una extensión. Los bloqueadores de anuncios y rastreadores son el caso común, y están instalados mucho más ampliamente de lo que la mayoría de los equipos asumen cuando miran sus propias máquinas.

Pero la categoría es más amplia que eso. El software de seguridad empresarial filtra peticiones antes de que salgan de la máquina. Algunos productos antivirus hacen lo mismo. Una configuración de proxy corporativo puede rechazar un host. La configuración del navegador en un dispositivo administrado puede bloquear una categoría en su totalidad. Todos estos producen el mismo error, y ninguno de ellos es algo que la persona que informa del problema sepa que tiene.

Lo que todos comparten es que se sientan entre tu código y la red, dentro del entorno del usuario, donde no tienes visibilidad ni control.

Tres errores que todos dicen bloqueado

Chromium tiene varios, no son intercambiables, y la diferencia es completamente sobre quién hizo el bloqueo. Estos son los tres que vale la pena conocer, con las descripciones de la propia lista de errores:

  • ERR_BLOCKED_BY_CLIENT: “El cliente decidió bloquear la petición.” Algo en la máquina del usuario. No la tuya, y no la del otro sitio.
  • ERR_BLOCKED_BY_CSP: “La petición fue bloqueada por una Content Security Policy.” Tu propia política, o una que un proxy añadió en el trayecto.
  • ERR_BLOCKED_BY_RESPONSE: la respuesta llegó llevando requisitos que no fueron cumplidos, como X-Frame-Options, Content-Security-Policy comprobaciones ancestrales o Cross-Origin-Resource-Policy. Ese es la decisión del otro extremo.

Tres errores, tres propietarios: la máquina del usuario, tu política, sus cabeceras. Solo el del medio es tuyo para arreglar, y es el que la gente busca en último lugar. Una cuarta familia se confunde con estas lo bastante a menudo para que merezca la pena separarla también: un error CORS no es un bloqueo en absoluto, es una respuesta que el navegador obtuvo y luego se negó a entregar a tu código.

Lo que tu JavaScript ve, que es casi nada

Esta es la parte que lo hace costoso. Una petición bloqueada no llega a tu código como “bloqueada”.

fetch() se rechaza con un TypeError, y MDN es explícito que esto es lo que obtienes para un error de red en general, “por ejemplo, porque el dispositivo no tiene conectividad”. El mismo rechazo cubre una URL mal formada, un dispositivo genuinamente sin conexión, y una petición que una extensión se negó a enviar. La promesa no lleva ninguna razón, así que no hay nada en el rechazo para diferenciar esos casos.

Vale la pena recordar qué no se rechaza mientras estás aquí: un estado de error HTTP no lo hace. Un 404 o un 504 se resuelve normalmente y te deja comprobando response.ok. Así que el fallo que produce la menor cantidad de información en tu código es el donde la petición nunca salió de la máquina, y los fallos que producen la mayor cantidad son los que tu servidor manejó.

Si quieres la distinción, el navegador la tiene. El panel de red muestra la petición con el error de red contra ella, y chrome://net-export lo captura. Ambos de esos están en la máquina del usuario, que es el problema del que trata todo este artículo.

Por qué es tan difícil de reproducir

No puedes reproducirlo, por construcción, porque la causa no está en tu sistema. Está en una configuración que existe en una máquina, elegida por alguien que puede que no recuerde haberla elegido, y bastante posiblemente instalada por su empleador.

Así que el bucle va: un usuario informa que guardar no hace nada, abres la página, guardar funciona, cierras el ticket. El usuario no se equivoca y tampoco tú. La diferencia es una lista de hosts bloqueados que no puedes ver.

Pedirles que desactiven extensiones a veces funciona y es un instrumento pobre. Les pide que experimenten en su propio navegador en tu nombre, solo encuentra el caso de la extensión, y un dispositivo administrado no les dejará hacerlo en absoluto.

Session Replay

Extensión gratuita de Chrome. Un clic en la página que se está portando mal captura la captura de pantalla, la consola y el registro de red, y te devuelve un enlace para pegar en el ticket.

Instalar la extensión

El registro de red es lo que lo resuelve, porque la petición que fue rechazada está en él con el error contra ella. Eso convierte “guardar no hace nada” en “esta petición a este host nunca salió de su máquina”, que es una conversación diferente y normalmente una corta.

Qué hacer al respecto

Espéralo en cualquier cosa que parezca rastreo. Los hosts con analytics, metrics, telemetry, beacon o ads en el nombre son bloqueados por defecto por listas comunes. También lo son las rutas. Si tu propio reporte de errores vive en tal host, los reportes sobre él son los que nunca recibirás.

No dejes que una petición bloqueada rompa la página. Una llamada de analytics bloqueada no debería detener un formulario enviándose. Separa la petición que importa de la petición que es agradable tener, y deja que la segunda falle en silencio.

Trata la ausencia de datos como datos. Menos eventos de un subconjunto de usuarios es una de las pocas señales que esto deja atrás, y se lee como una semana tranquila en lugar de como una culpa.

En un párrafo

ERR_BLOCKED_BY_CLIENT significa que algo en la máquina del visitante, normalmente una extensión y a veces el software de su empleador, decidió que tu petición no sería enviada. Tu servidor nunca la vio, fetch la reporta como el mismo TypeError que usa para un dispositivo sin conexión, y el único registro está en una máquina que no tienes. No es lo mismo que un bloqueo de política que posees o una cabecera que el otro extremo envió, y diferenciar esos tres es la mayor parte del trabajo.