Ayer añadimos botones de compartir al pie de cada entrada de esta bitácora. Nada del otro mundo: seis redes, seis asistentes de IA, cada uno con la URL del artículo metida en su parámetro. Luego, por costumbre, pasamos la herramienta sobre la carpeta compilada del sitio antes de dar el cambio por bueno.
Doce avisos nuevos. INDEX-NOFOLLOW-INTERNAL, severidad media, una vez por cada entrada del
devlog. La regla dice que hay un enlace interno marcado con nofollow, que es una forma de
pedirle a un buscador que no siga un enlace de tu propia web hacia tu propia web. Suele ser un
descuido, y por eso la regla existe.
El problema es que ninguno de esos enlaces era interno. Los trece enlaces que añadimos van a
twitter.com, linkedin.com, chatgpt.com, grok.com. Todos llevan rel="nofollow noopener",
que es exactamente lo que hay que poner en un botón de compartir. La regla estaba señalando algo
que no existía.
Cuatro de trece
Lo primero fue mirar cuáles. De los trece enlaces del bloque, la herramienta solo se quejaba de cuatro:
https://chatgpt.com/?q=… ← señalado
https://www.perplexity.ai/?q=… ← señalado
https://grok.com/?q=… ← señalado
mailto:?subject=… ← señalado
https://claude.ai/new?q=… ← correcto
https://chat.mistral.ai/chat?q=… ← correcto
https://twitter.com/intent/tweet?… ← correcto
https://www.google.com/search?… ← correcto
Cuesta un rato verlo. Los cuatro señalados tienen la ruta vacía: después del dominio va
directamente la interrogación. Los ocho correctos tienen una ruta de por medio, /new, /chat,
/intent/tweet, /search.
Con eso ya se puede escribir un caso mínimo, que es lo que separa una sospecha de un fallo. Cinco enlaces en una página de prueba, cinco maneras de enlazar fuera de casa, y a ver a dónde dice la herramienta que van:
| Enlace escrito en el HTML | A dónde lo resolvía |
|---|---|
https://ejemplo-a.com/?q=1 |
https://propio.local/ |
https://ejemplo-b.com?q=1 |
https://propio.local/ |
https://ejemplo-c.com/ruta?q=1 |
correcto |
https://ejemplo-d.com/ |
https://propio.local/ |
https://ejemplo-e.com/?utm_source=boletin |
https://propio.local/ |
Cuatro de cinco. Y el cuarto es el que hiela: https://ejemplo-d.com/, un enlace normal y
corriente a la portada de otra web, se estaba registrando como un enlace a nuestra propia
portada. El quinto es peor todavía, porque un ?utm_source= al final es lo que lleva la mitad
de los enlaces que salen de un boletín.
Por qué
El modo que audita una carpeta compilada tiene una función que traduce URLs a ficheros. Existe
porque un servidor de ficheros estáticos sirve /about, /about/ y /about/index.html desde el
mismo sitio, y si la herramienta no las unifica acaba auditando la misma página tres veces y
disparando reglas de contenido duplicado que ella misma ha inventado.
Esa función miraba una cosa sola: la ruta de la URL. Cogía url.path(), buscaba el fichero
correspondiente dentro de la carpeta y devolvía la URL publicada de ese fichero. Del host no
preguntaba nada.
Así que https://chatgpt.com/?q=… entraba, la función se quedaba con la ruta /, encontraba
index.html en la raíz de nuestro dist/ y devolvía la portada del sitio auditado. El dominio se
evaporaba por el camino.
La colisión tampoco se limita a la barra sola. Cualquier ruta que exista dentro de la carpeta se
comporta igual: si tienes un dist/blog/index.html y alguien enlaza a https://ajeno.example/blog/,
ese enlace pasa a ser tuyo.
Lo que se perdía por el camino
El aviso falso era lo de menos. Un consultor que ve doce avisos raros los mira, entiende que son botones de compartir y sigue con su trabajo.
Lo caro estaba en el otro lado. Esos enlaces externos desaparecían del grafo del rastreo: si la herramienta cree que un enlace apunta a tu portada, nunca va a pedirle el estado al servidor ajeno, y la regla que avisa de enlaces externos rotos no puede ver lo que no existe. En un sitio con cien enlaces salientes a portadas de otros dominios, cien enlaces sin comprobar y nadie te avisa.
Y como contrapartida, esos mismos enlaces engordaban el recuento de enlaces entrantes de la portada, que es una señal que otras reglas leen para decidir si una página está bien conectada.
El número lo dio la propia auditoría. Antes del arreglo, el sitio compilado daba 133 URLs. Después, 171. Esas 38 son los enlaces externos que la herramienta se estaba tragando en una web de treinta y una páginas.
El arreglo, y la parte incómoda
Una comparación. En el modo de carpeta compilada, si el origen de la URL no coincide con el del sitio auditado, esa URL no es un fichero de aquí y no hay nada que traducir.
Escribimos dos tests de regresión, y antes de darlos por buenos revertimos el arreglo para verlos fallar. Los dos se pusieron en rojo con el mensaje que tenían que dar, y solo entonces volvimos a poner la línea. Un test que nunca has visto fallar no sabes si comprueba algo.
Lo incómodo viene ahora. Es la tercera vez que este proyecto decide algo mirando el texto de una ruta o de un nombre en lugar de mirar a dónde apunta de verdad, y la segunda en un mes. Las dos anteriores están escritas en la revisión del 4 de agosto: una criba de red que comparaba nombres de host en lugar de direcciones resueltas, y un perímetro que decidía por el texto del host en vez de por la dirección marcada.
Tres veces el mismo error de razonamiento, en tres sitios distintos del código, escrito por la misma cabeza. Lo hemos convertido en pregunta fija de revisión: esta comparación, ¿mira el texto o mira a dónde apunta?
Qué versión lo lleva
La 0.8.0, y sube de minor en vez de patch por una razón que a lo mejor te afecta: una regla deja de
dispararse. Si tenías una comprobación en integración continua contando hallazgos de
INDEX-NOFOLLOW-INTERNAL, el número te va a bajar. No es que hayas arreglado nada; es que antes
contabas humo.
El resto de la historia es la de siempre en esta bitácora. Mil veintiséis tests en verde, cincuenta y nueve reglas con su caso de prueba, y el fallo lo encontró alguien mirando una web de verdad con la herramienta puesta. Ninguna prueba unitaria iba a cazarlo, porque la lógica de la regla era correcta: el defecto estaba en la costura entre el motor y ella.
Auditar tu propio sitio con tu propio producto no es una demostración simpática para la portada. Es la única forma que hemos encontrado de que estas cosas salgan antes que después.