Saltar al contenido
crawlforgeEnglish
Mediciones28 de octubre de 20266 min de lecturaRead in English

Donde Google deja de leer

La 0.19.0 avisa cuando el canonical, el robots o el hreflang quedan fuera del head. Para saber si acertaba, la comparé con un parser del estándar completo.

Un <link rel="canonical"> puede estar en el código fuente y no valer nada. Pasa cuando algo que no pertenece al <head> aparece antes que él: un <img> de seguimiento, un <div>, un <iframe> o una palabra suelta que dejó un plugin. El navegador cierra el head en ese punto y abre el body, aunque el </head> siga más abajo, y todo lo que venía detrás pasa a estar en el body. Google lo explica en su página sobre metadatos válidos: si usas un elemento no válido en el head, ignora todo lo que aparece después.

Nadie lo ve, porque al abrir el código fuente el canonical está donde debe. La 0.19.0 trae la regla que lo detecta, META-OUTSIDE-HEAD. Es high y del nivel gratuito, y el catálogo pasa a 66 reglas. Vigila los cinco metadatos que importan para el posicionamiento: el canonical, el meta robots, el hreflang, el título y la descripción.

Lo que dice el hallazgo

El hallazgo nombra lo que cerró el head, con el atributo que lo identifica: <img src="https://…">, <div id="…"> o el texto que sobraba. En un WordPress casi siempre es un plugin, y ese atributo dice cuál. Saber que el canonical no vale sirve de poco si después hay que abrir el HTML a mano para encontrar el culpable.

La regla también mira las páginas que el rastreo ve como noindex, a diferencia de casi todas las demás. El motivo es que ese noindex puede ser una de las etiquetas perdidas. Si lo es, Google no lo ve y la página que nosotros damos por excluida puede estar indexada.

Seguir el estándar, no una lista

La versión ingenua de esta regla es una lista: en el head solo caben title, meta, link, script, style, base, noscript y template, y cualquier otra cosa lo cierra. Así lo resume Google, pero hay casos en los que la lista se equivoca, y son de los que más aparecen.

El píxel de Meta es el primero. Su código de instalación pone un <noscript> con un <img> dentro, y mucha gente lo pega en el head. Con la lista, ese <img> cerraría el head en todas las páginas que lo instalan así, y no tienen ningún problema. El estándar HTML trata el contenido de <noscript> como texto cuando el navegador ejecuta JavaScript, así que ese <img> no cierra nada. Lo mismo vale para el contenido de <template>, <script> y <style>.

El segundo es menos intuitivo. Un <link> o un <meta> que aparece entre </head> y <body> no se queda fuera: el estándar lo devuelve al head. Lo que abre el body en esa zona es otro tipo de elemento, o un texto que no sea espacio en blanco.

Y luego está el caso que más me preocupaba: WordPress imprimiendo un aviso de PHP antes del documento, algo como <br /><b>Warning</b>: Undefined variable…. Ese <br> aparece antes incluso que <html>, el head no llega a abrirse y todos los metadatos de la página caen en el body. Ahí no llega ningún selector, así que el motor repasa los bytes anteriores a <html> antes de empezar.

Un oráculo para el parser

El motor lee el HTML con lol_html, que va en streaming y no construye el árbol del documento. Eso lo hace rápido, pero también significa que las reglas del estándar sobre dónde acaba el head las tuve que escribir yo. Hacía falta algo con qué comparar.

En el node_modules de esta misma web está parse5, un parser que implementa el estándar WHATWG completo, el mismo que siguen los navegadores. Escribí un script que le pasa una carpeta de HTML y apunta qué metadatos quedan fuera del head según él, y lo comparé fichero a fichero con lo que decía la regla.

El corpus tenía dos partes. La primera, 26 casos escritos a mano para que fueran difíciles: etiquetas en mayúsculas, un &nbsp; suelto, un <svg> con su propio <title>, cierres sueltos como </p>, un elemento personalizado, texto antes del doctype o el aviso de PHP. La segunda, las portadas de 81 sitios reales: los dominios externos que enlaza colorvivo.com y los 29 WordPress del hub de ColorVivo, con una sola petición a cada uno.

Coinciden en todos los ficheros menos uno. Es un </br> suelto dentro del head. El estándar lo convierte en un <br> y abre el body, pero lol_html no avisa de las etiquetas de cierre que no abren nada, así que el motor no lo ve. Es un fallo de los que se callan, no de los que acusan en falso, y queda escrito en el changelog.

El oráculo también encontró algo que yo no había previsto. La primera versión del repaso previo daba un falso positivo en decoracion2.com. Su plantilla empieza con los comentarios condicionales de Internet Explorer, <!--[if IE 8]><html class="ie8">, y el primer <html del fichero está dentro de un comentario. Mi código buscaba ese <html y cortaba ahí, partiendo el comentario por la mitad. Lo reescribí para que avance desde el principio saltando comentarios enteros, y el test que lo comprueba usa esa cabecera tal cual.

Ninguna de las 81 portadas tenía el defecto, y tampoco las 600 páginas de colorvivo que rastreé ni especialmundial.com. La regla está comprobada contra los falsos positivos, pero todavía no ha encontrado un caso real. Cuando lo encuentre, lo contaré.

Lo que cuesta

La primera versión registraba el manejador con el selector *, que es lo más sencillo: se llama para cada elemento de la página y vuelve enseguida si el head ya se cerró. Lo medí parseando cuatro portadas reales, de 400 a 545 KB, alternando la 0.18.0 y la versión nueva. El parseo subía entre un 8 y un 13 %. Aunque cada llamada vuelva en la primera comprobación, sigue siendo una llamada por cada elemento del body.

Basta con que lol_html solo invoque el manejador en head *, html > *, que son los descendientes del head y los hijos directos de <html>, y con mirar el texto solo dentro del head. Con eso el coste bajó a entre un 3 y un 7 %, unos 0,03 milisegundos por página. La medición se hizo con el Mac muy cargado, así que tómala como orden de magnitud. El banco de regresión que corre antes de cada publicación dio 68.519 elementos por segundo, en la línea de las últimas versiones.

Lo comprobé todo en rojo antes de publicar, rompiendo cada pieza y viendo fallar su test: el texto suelto, el <noscript>, la marca de orden de bytes que algunos editores ponen al principio del fichero, el selector restringido, el repaso previo, el filtro de páginas noindex y el cableado del motor. El CI pasó en Linux, Windows y macOS.

Se construye en público

Cada dos semanas: mediciones, fallos y ejemplos de uso. Nada más.

← Volver a la bitácora