Descubre qué es el escáner de precarga del navegador, cómo ayuda al rendimiento y cómo puedes mantenerte lejos de tu camino.
Un aspecto ignorado de la optimización de la velocidad de la página implica conocer un poco sobre los componentes internos del navegador. Los navegadores realizan determinadas optimizaciones para mejorar el rendimiento de maneras que nosotros no podemos hacerlo nosotros, pero solo siempre y cuando esas optimizaciones no se eviten involuntariamente.
Una optimización interna del navegador que debes comprender es el escáner de precarga del navegador. En esta publicación, se explica cómo funciona el escáner de precarga y, lo que es más importante, cómo puedes evitar que se interponga.
¿Qué es un escáner de precarga?
Cada navegador tiene un analizador de HTML principal que asigna tokens a lenguaje de marcado sin procesar y lo procesa en un modelo de objeto. Todo esto sucede hasta que el analizador se detiene cuando encuentra un recurso de bloqueo, como una hoja de estilo cargada con un elemento <link>
o una secuencia de comandos cargada con un elemento <script>
sin un atributo async
o defer
.
<link>
para un archivo CSS externo, que impide que el navegador analice el resto del documento (o incluso procese el resto) hasta que se descargue y analice el código CSS.
En el caso de los archivos CSS, el análisis y la renderización se bloquean para evitar un flash de contenido sin estilo (FOUC), que ocurre cuando se puede ver brevemente una versión sin estilo de una página antes de que se le apliquen estilos.
![La página principal de web.dev en un estado sin estilo (izquierda) y con el estado con estilo (derecha).](https://web.developers.google.cn/static/articles/preload-scanner/image/the-webdev-home-page-an-d03ddaf5d79f7.png?authuser=5&hl=es)
El navegador también bloquea el análisis y la renderización de la página cuando encuentra elementos <script>
sin un atributo defer
o async
.
Esto se debe a que el navegador no puede saber con seguridad si alguna secuencia de comandos determinada modificará el DOM mientras el analizador de HTML principal sigue realizando su trabajo. Por eso es habitual cargar tu JavaScript al final del documento para que los efectos del análisis y la renderización bloqueados se vuelvan marginales.
Estos son buenos motivos por los que el navegador debe bloquear tanto el análisis como la renderización. Sin embargo, no se recomienda bloquear cualquiera de estos pasos importantes, ya que pueden retrasar el descubrimiento de otros recursos importantes. Afortunadamente, los navegadores hacen todo lo posible para mitigar estos problemas mediante un analizador de HTML secundario llamado escáner de precarga.
<body>
, pero el escáner de precarga puede mirar hacia adelante en el lenguaje de marcado sin procesar para encontrar ese recurso de imagen y comenzar a cargarlo antes de que se desbloquee el analizador de HTML principal.
El rol de un escáner de precarga es especulativo, lo que significa que examina el lenguaje de marcado sin procesar para encontrar recursos que se recuperarán de manera oportuna antes de que el analizador HTML principal los descubra.
Cómo saber cuándo funciona el análisis de precarga
El escáner de precarga existe debido al procesamiento y análisis bloqueados. Si estos dos problemas de rendimiento nunca existieran, el análisis de precarga no sería muy útil. La clave para averiguar si una página web se beneficia del escáner de carga previa depende de estos fenómenos de bloqueo. Para ello, puedes introducir un retraso artificial en las solicitudes para averiguar dónde funciona el análisis de precarga.
Toma esta página de imágenes y texto básicos con una hoja de estilo como ejemplo. Debido a que los archivos CSS bloquean tanto la representación como el análisis, introduce un retraso artificial de dos segundos para la hoja de estilo a través de un servicio de proxy. Este retraso hace que sea más fácil ver en la cascada de red donde funciona el escáner de precarga.
![El gráfico de cascada de la red WebPageTest muestra un retraso artificial de 2 segundos que se impone en la hoja de estilo.](https://web.developers.google.cn/static/articles/preload-scanner/image/the-webpagetest-network-w-888b82cd76bf1.png?authuser=5&hl=es)
Como puedes ver en la cascada, el escáner de precarga detecta el elemento <img>
incluso cuando la renderización y el análisis de documentos están bloqueados. Sin esta optimización, el navegador no puede recuperar elementos de forma oportuna durante el período de bloqueo, y más solicitudes de recursos serían consecutivas en lugar de simultáneas.
Con ese ejemplo de prueba, veamos algunos patrones del mundo real en los que se puede derrotar el escáner de precarga y qué se puede hacer para corregirlos.
Se insertaron async
secuencias de comandos
Supongamos que tienes HTML en tu <head>
que incluye JavaScript intercalado como este:
<script>
const scriptEl = document.createElement('script');
scriptEl.src = '/yall.min.js';
document.head.appendChild(scriptEl);
</script>
Las secuencias de comandos inyectadas son async
de forma predeterminada, por lo que, cuando se inserte esta secuencia, se comportará como si se le aplicara el atributo async
. Esto significa que se ejecutará tan pronto como sea posible y no bloqueará la renderización. Suena óptimo, ¿verdad? Sin embargo, si supones que este <script>
intercalado viene después de un elemento <link>
que carga un archivo CSS externo, obtendrás un resultado deficiente:
![Este gráfico de WebPageTest muestra el análisis de precarga que se interrumpe cuando se inserta una secuencia de comandos.](https://web.developers.google.cn/static/articles/preload-scanner/image/this-webpagetest-chart-sh-d83c16fca193.png?authuser=5&hl=es)
async
insertada. El escáner de precarga no puede descubrir la secuencia de comandos durante la fase de bloqueo de la renderización, ya que se inserta en el cliente.
Analicemos lo que sucedió aquí:
- A los 0 segundos, se solicita el documento principal.
- A los 1.4 segundos, llega el primer byte de la solicitud de navegación.
- A los 2.0 segundos, se solicitan el CSS y la imagen.
- Debido a que el analizador está bloqueado cuando se carga la hoja de estilo y el JavaScript intercalado que inserta la secuencia de comandos
async
aparece después de esa hoja de estilo a los 2.6 segundos, la funcionalidad que proporciona la secuencia de comandos no está disponible en cuanto podría.
Esto no es óptimo, ya que la solicitud de la secuencia de comandos se realiza solo después de que se termina de descargar la hoja de estilo. Esto retrasa la ejecución de la secuencia de comandos lo antes posible. Por el contrario, como el elemento <img>
es detectable en el lenguaje de marcado proporcionado por el servidor, lo detecta el escáner de precarga.
Entonces, ¿qué sucede si usas una etiqueta <script>
normal con el atributo async
en lugar de insertar la secuencia de comandos en el DOM?
<script src="/yall.min.js" async></script>
Este es el resultado:
![Una cascada de red de WebPageTest que muestra cómo una secuencia de comandos asíncrona cargada con el elemento de secuencia de comandos HTML aún es detectable por el escáner de precarga del navegador, aunque el analizador de HTML principal del navegador esté bloqueado mientras se descarga y procesa una hoja de estilo.](https://web.developers.google.cn/static/articles/preload-scanner/image/a-webpagetest-network-wat-02055245d5ef.png?authuser=5&hl=es)
<script>
async
. El escáner de precarga detecta la secuencia de comandos durante la fase de bloqueo de la representación y la carga de forma simultánea con el CSS.
Es posible que sientas la tentación de sugerir que estos problemas se podrían solucionar usando rel=preload
. Esto funcionaría, pero puede tener algunos efectos secundarios. Después de todo, ¿por qué usar rel=preload
para solucionar un problema que se puede evitar no inyectando un elemento <script>
en el DOM?
![Una cascada de WebPageTest que muestra cómo se usa la sugerencia del recurso rel=preload para promover el descubrimiento de una secuencia de comandos insertada asíncrona, aunque de una manera que puede tener efectos secundarios no deseados.](https://web.developers.google.cn/static/articles/preload-scanner/image/a-webpagetest-waterfall-s-2f698a04955f.png?authuser=5&hl=es)
async
insertada, pero la secuencia de comandos async
está precargada para garantizar que se descubra antes.
Precargando "correcciones" Este es el problema, pero presenta un problema nuevo: la secuencia de comandos async
en las dos primeras demostraciones, a pesar de estar cargada en <head>
, se carga en "Baja". la hoja de estilo, mientras que la hoja de estilo se carga en "Mayor" prioridad. En la última demostración, en la que se precargó la secuencia de comandos de async
, la hoja de estilo se sigue cargando en la posición "Máxima" pero la de la secuencia de comandos se ascendió a "Alta".
Cuando se aumenta la prioridad de un recurso, el navegador le asigna más ancho de banda. Esto significa que, aunque la hoja de estilo tenga la prioridad más alta, la prioridad elevada de la secuencia de comandos puede provocar contención del ancho de banda. Eso podría ser un factor de las conexiones lentas o en los casos en que los recursos son bastante grandes.
La respuesta aquí es directa: si se necesita una secuencia de comandos durante el inicio, no elimines el escáner de precarga inyectándola en el DOM. Experimenta según sea necesario con la posición de los elementos <script>
y con atributos como defer
y async
.
Carga diferida con JavaScript
La carga diferida es un excelente método para conservar datos, que a menudo se aplica a imágenes. Sin embargo, a veces la carga diferida se aplica de forma incorrecta a las imágenes que están en la "mitad superior de la página", por así decirlo.
Esto presenta problemas potenciales con la visibilidad de recursos en relación con el escáner de precarga y puede retrasar innecesariamente el tiempo que se tarda en descubrir una referencia a una imagen, descargarla, decodificarla y presentarla. Tomemos este lenguaje de marcado de imágenes como ejemplo:
<img data-src="/sand-wasp.jpg" alt="Sand Wasp" width="384" height="255">
El uso de un prefijo data-
es un patrón común en los cargadores diferidos con tecnología JavaScript. Cuando la imagen se desplaza en el viewport, el cargador diferido quita el prefijo data-
, lo que significa que, en el ejemplo anterior, data-src
se convierte en src
. Esta actualización solicita al navegador que recupere el recurso.
Este patrón no es problemático hasta que se aplica a las imágenes que están en el viewport durante el inicio. Debido a que el escáner de precarga no lee el atributo data-src
de la misma manera que lo haría con un atributo src
(o srcset
), la referencia de imagen no se detecta antes. Y, peor aún, la carga de la imagen se retrasa hasta después de que el cargador diferido JavaScript se descarga, compila y ejecuta.
![Un gráfico de cascada de red de WebPageTest que muestra cómo una imagen con carga diferida que se encuentra en el viewport durante el inicio se retrasa necesariamente porque el escáner de precarga del navegador no puede encontrar el recurso de imagen y solo se carga cuando se carga el código JavaScript necesario para que funcione la carga diferida. La imagen se descubre mucho después de lo que debería.](https://web.developers.google.cn/static/articles/preload-scanner/image/a-webpagetest-network-wat-6e823c2a1d7a6.png?authuser=5&hl=es)
Según el tamaño de la imagen, que puede depender del tamaño del viewport, puede ser un elemento candidato para el Procesamiento de imagen con contenido más grande (LCP). Cuando el escáner de precarga no puede recuperar especulativamente el recurso de imagen con anticipación(posiblemente durante el punto en el que se renderizan en bloque las hojas de estilo de la página), el LCP se ve afectado.
La solución es cambiar el lenguaje de marcado de la imagen:
<img src="/sand-wasp.jpg" alt="Sand Wasp" width="384" height="255">
Este es el patrón óptimo para las imágenes que están en el viewport durante el inicio, ya que el escáner de precarga descubrirá y recuperará el recurso de imagen más rápidamente.
![Un gráfico de cascada de red de WebPageTest que muestra una situación de carga para una imagen en el viewport durante el inicio. La imagen no se carga de forma diferida, lo que significa que no depende de la secuencia de comandos para cargarse, lo que significa que el escáner de precarga puede descubrirla antes.](https://web.developers.google.cn/static/articles/preload-scanner/image/a-webpagetest-network-wat-5e2602b834b47.png?authuser=5&hl=es)
El resultado de este ejemplo simplificado es una mejora de 100 milisegundos en el LCP con conexión lenta. Esto puede no parecer una gran mejora, pero es cuando consideras que la solución es una corrección rápida en el lenguaje de marcado y que la mayoría de las páginas web son más complejas que este conjunto de ejemplos. Eso significa que los candidatos de LCP pueden tener que lidiar con el ancho de banda con muchos otros recursos, por lo que optimizaciones como esta se vuelven cada vez más importantes.
Imágenes de fondo de CSS
Recuerda que el análisis de precarga del navegador analiza el lenguaje de marcado. No analiza otros tipos de recursos, como CSS, que pueden implicar recuperaciones de imágenes a las que hace referencia la propiedad background-image
.
Al igual que HTML, los navegadores procesan CSS en su propio modelo de objetos, conocido como CSSOM. Si se descubren recursos externos mientras se construye el CSSOM, estos se solicitan en el momento del descubrimiento y no a través del escáner de precarga.
Supongamos que el Candidato de LCP de tu página es un elemento con una propiedad background-image
de CSS. Cuando se cargan los recursos, ocurre lo siguiente:
![Un gráfico de cascada de red de WebPageTest que muestra una página con un candidato de LCP cargado desde CSS con la propiedad background-image. Debido a que la imagen del candidato de LCP se encuentra en un tipo de recurso que el escáner de precarga del navegador no puede examinar, el recurso tarda en cargarse hasta que se descarga y procesa la CSS, lo que retrasa el tiempo de procesamiento del candidato de LCP.](https://web.developers.google.cn/static/articles/preload-scanner/image/a-webpagetest-network-wat-ca60b2c25fa52.png?authuser=5&hl=es)
background-image
de CSS (fila 3). La imagen que solicita no comienza a recuperarse hasta que el analizador de CSS la encuentra.
En este caso, el escáner de precarga no está tan frustrado porque no está involucrado. Aun así, si un candidato de LCP de la página es de una propiedad de CSS background-image
, te recomendamos precargar esa imagen:
<!-- Make sure this is in the <head> below any
stylesheets, so as not to block them from loading -->
<link rel="preload" as="image" href="lcp-image.jpg">
Esa sugerencia rel=preload
es pequeña, pero ayuda al navegador a descubrir la imagen antes de lo que lo haría de otro modo:
![Un gráfico de cascada de red de WebPageTest que muestra una imagen de fondo de CSS (que es la candidata para LCP) que se carga mucho antes debido al uso de una sugerencia rel=preload. El tiempo de LCP mejora en aproximadamente 250 milisegundos.](https://web.developers.google.cn/static/articles/preload-scanner/image/a-webpagetest-network-wat-8a47e283c7c66.png?authuser=5&hl=es)
background-image
de CSS (fila 3). La sugerencia rel=preload
ayuda al navegador a descubrir la imagen unos 250 milisegundos antes que sin la sugerencia.
Con la sugerencia rel=preload
, el candidato de LCP se descubre antes, lo que reduce el tiempo de LCP. Si bien esa sugerencia ayuda a solucionar el problema, la mejor opción puede ser evaluar si tu candidato de LCP de imagen tiene que cargarse desde CSS. Con una etiqueta <img>
, tendrás más control sobre la carga de una imagen que sea adecuada para el viewport, a la vez que permitirás que el escáner de precarga la descubra.
Alinea demasiados recursos
El intercalado es una práctica que coloca un recurso dentro del HTML. Con la codificación base64, puedes intercalar hojas de estilo en elementos <style>
, secuencias de comandos en elementos <script>
y prácticamente cualquier otro recurso.
Integrar recursos puede ser más rápido que descargarlos, ya que no se emite una solicitud separada para el recurso. Está directamente en el documento y se carga al instante. Sin embargo, existen desventajas importantes:
- Si no almacenas en caché tu HTML (y no puedes hacerlo si la respuesta HTML es dinámica), los recursos intercalados nunca se almacenan en caché. Esto afecta el rendimiento porque los recursos intercalados no se pueden reutilizar.
- Incluso si puedes almacenar HTML en caché, los recursos intercalados no se comparten entre documentos. Esto reduce la eficiencia del almacenamiento en caché en comparación con los archivos externos que se pueden almacenar en caché y volver a usar en todo un origen.
- Si intercala demasiado, retrasarás el escáner de precarga para que no descubra recursos más adelante en el documento, ya que descargar ese contenido adicional intercalado tomará más tiempo.
Tomemos esta página como ejemplo. En determinadas condiciones, el candidato de LCP es la imagen de la parte superior de la página, y el CSS se encuentra en un archivo separado cargado por un elemento <link>
. La página también utiliza cuatro fuentes web que se solicitan como archivos separados del recurso CSS.
![Un gráfico de cascada de red de WebPageTest de la página con un archivo CSS externo con cuatro fuentes a las que se hace referencia. En el plazo previsto, el escáner de precarga detecta la imagen del candidato de LCP.](https://web.developers.google.cn/static/articles/preload-scanner/image/a-webpagetest-network-wat-ebe04c09d25a6.png?authuser=5&hl=es)
<img>
, pero el escáner de precarga la descubre porque la CSS y las fuentes necesarias para la página se cargan en recursos separados, lo que no retrasa el escáner de precarga en su trabajo.
¿Qué sucede si el CSS y todas las fuentes se intercalan como recursos Base64?
![Un gráfico de cascada de red de WebPageTest de la página con un archivo CSS externo con cuatro fuentes a las que se hace referencia. El escáner de precarga se retrasa significativamente desde el descubrimiento de la imagen de LCP .](https://web.developers.google.cn/static/articles/preload-scanner/image/a-webpagetest-network-wat-2cad0c33c7bdd.png?authuser=5&hl=es)
<img>
, pero el intercalado del CSS y sus cuatro recursos de fuente en `` retrasan el escáner de precarga para que no descubra la imagen hasta que esos recursos se descarguen por completo.
El impacto de la intercalación tiene consecuencias negativas para el LCP en este ejemplo y para el rendimiento en general. La versión de la página que no intercala nada pinta la imagen LCP en unos 3.5 segundos. La página que intercala todo no pinta la imagen de LCP sino hasta un poco más de 7 segundos.
No se trata solo del escáner de precarga. Incorporar fuentes no es una buena estrategia porque base64 es un formato ineficiente para recursos binarios. Otro factor en juego es que los recursos de fuentes externas no se descargan a menos que el CSSOM los determine necesarios. Cuando esas fuentes se intercalan como base64, se descargan ya sea que sean necesarias para la página actual o no.
¿La precarga podría mejorar esto? Por supuesto. Podrías precargar la imagen de LCP y reducir el tiempo de LCP, pero sobredimensionar el código HTML potencialmente no almacenable en caché con recursos intercalados tiene otras consecuencias en el rendimiento negativas. Este patrón también afecta el First Contentful Paint (FCP). En la versión de la página donde no hay nada intercalado, el FCP es de aproximadamente 2.7 segundos. En la versión en la que todo está intercalado, el FCP es de aproximadamente 5.8 segundos.
Ten mucho cuidado al incorporar elementos en HTML, especialmente los recursos codificados en Base64. En general, no se recomienda, excepto para recursos muy pequeños. En línea lo menos posible, ya que demasiado se juega con fuego.
Renderiza lenguaje de marcado con JavaScript del cliente
No hay duda al respecto: JavaScript indudablemente afecta la velocidad de la página. Los desarrolladores no solo dependen de él para proporcionar interactividad, sino que también ha habido una tendencia a confiar en él para entregar el contenido en sí. Esto lleva a una mejor experiencia del desarrollador en algunos aspectos. pero los beneficios para los desarrolladores no siempre se traducen en beneficios para los usuarios.
Un patrón que puede frustrar el escáner de precarga es renderizar el lenguaje de marcado con JavaScript del cliente:
![Una cascada de red de WebPageTest que muestra una página básica con imágenes y texto renderizados por completo en el cliente en JavaScript. Debido a que el lenguaje de marcado está contenido en JavaScript, el escáner de carga previa no puede detectar ninguno de los recursos. Además, todos los recursos se retrasan debido al tiempo adicional de red y de procesamiento que requieren los frameworks de JavaScript.](https://web.developers.google.cn/static/articles/preload-scanner/image/a-webpagetest-network-wat-9a4b1c22879f5.png?authuser=5&hl=es)
Cuando JavaScript contiene y renderiza por completo las cargas útiles de marcado en el navegador, cualquier recurso de ese lenguaje de marcado es invisible para el escáner de precarga. Esto retrasa el descubrimiento de recursos importantes, lo que sin duda afecta al LCP. En el caso de estos ejemplos, la solicitud de la imagen LCP se retrasa significativamente en comparación con la experiencia equivalente renderizada por el servidor que no requiere que JavaScript aparezca.
Esto se desvía un poco del enfoque de este artículo, pero los efectos de renderizar el lenguaje de marcado en el cliente van mucho más allá de vencer al escáner de precarga. Por un lado, la introducción de JavaScript para potenciar una experiencia que no requiere un tiempo de procesamiento innecesario que puede afectar la Interacción a la siguiente pintura (INP). Si procesas grandes cantidades de lenguaje de marcado en el cliente, es más probable que se generen tareas largas en comparación con la misma cantidad de lenguaje de marcado que envía el servidor. El motivo, además del procesamiento adicional que implica JavaScript, es que los navegadores transmiten el lenguaje de marcado desde el servidor y fragmentan el procesamiento de tal manera que tiende a limitar las tareas largas. Por otro lado, el lenguaje de marcado renderizado por el cliente se maneja como una tarea única y monolítica, lo que puede afectar el INP de una página.
La solución para esta situación depende de la respuesta a esta pregunta: ¿Por qué el servidor no puede proporcionar el lenguaje de marcado de tu página en lugar de renderizarlo en el cliente? Si la respuesta es “no”, se debe considerar la renderización del servidor (SSR) o el lenguaje de marcado generado de forma estática siempre que sea posible, ya que ayudará al escáner de precarga a descubrir y recuperar oportunmente recursos importantes con anticipación.
Si tu página necesita JavaScript para adjuntar la funcionalidad a algunas partes del lenguaje de marcado de tu página, puedes hacerlo con SSR, ya sea con JavaScript normal o con la hidratación para obtener lo mejor de ambos mundos.
Ayudar al escáner de precarga a ayudarte
El escáner de precarga es una optimización de navegador altamente eficaz que ayuda a que las páginas se carguen más rápido durante el inicio. Al evitar los patrones que pierden su capacidad de descubrir recursos importantes con anticipación, no solo simplificas el desarrollo para ti, sino que también creas mejores experiencias del usuario que ofrecen mejores resultados en muchas métricas, incluidas algunas métricas web.
En resumen, ten en cuenta lo siguiente de esta publicación:
- El escáner de precarga del navegador es un analizador de HTML secundario que analiza por adelantado al principal si está bloqueado para descubrir oportunamente recursos que puede recuperar más rápido.
- El escáner de precarga no puede detectar los recursos que no están presentes en el lenguaje de marcado que proporciona el servidor en la solicitud de navegación inicial. Estas son algunas maneras de neutralizar el escáner de precarga:
- Inyectar recursos en el DOM con JavaScript, ya sean secuencias de comandos, imágenes, hojas de estilo o cualquier otra cosa que sería mejor en la carga útil inicial del lenguaje de marcado del servidor.
- Carga diferida de imágenes o iframes de la mitad superior de la página con una solución de JavaScript.
- Representar lenguaje de marcado en el cliente que puede contener referencias a subrecursos de documentos mediante JavaScript
- El escáner de precarga solo analiza HTML. No examina el contenido de otros recursos, en particular los CSS, que pueden incluir referencias a recursos importantes, incluidos los candidatos de LCP.
Si, por algún motivo, no puedes evitar un patrón que afecte negativamente la capacidad del escáner de precarga para acelerar el rendimiento de carga, ten en cuenta la sugerencia del recurso rel=preload
. Si sí usas rel=preload
, realiza pruebas en las herramientas del lab para asegurarte de que se genera el efecto deseado. Por último, no precargues demasiados recursos, porque cuando priorizas todo, nada lo será.
Recursos
- "Secuencias de comandos asíncronas" insertadas por secuencia de comandos considerado perjudicial
- Cómo el precargador del navegador hace que las páginas se carguen más rápido
- Precarga los recursos críticos para mejorar la velocidad de carga
- Establece conexiones de red con anticipación para mejorar la velocidad percibida de la página
- Cómo optimizar el procesamiento de imagen con contenido más grande
Hero image de Unsplash, de Mohammad Rahmani