Forced reflow en Lighthouse: qué significa y cómo corregirlo
Un script que cambia la página y luego pide un tamaño hace que el navegador rehaga el layout de inmediato. Cuándo hace que las pulsaciones se congelen y cuándo es inofensivo.
¿Qué significa Forced reflow en Lighthouse?
Un forced reflow significa que un script cambió la página y luego pidió un tamaño, así que el navegador tuvo que recalcular el layout de inmediato. Lighthouse lista esos scripts en el insight Forced reflow ("A forced reflow occurs when JavaScript queries geometric properties"). Una vez es normal; en un bucle hace que las pulsaciones se congelen. Deja que CSS haga el trabajo del script, o lee todos los tamaños antes de cambiar nada.
Table of Contents!
¿Qué muestra el aviso de Forced reflow?
Lighthouse 13 y PageSpeed Insights lo describen así: "A forced reflow occurs when JavaScript queries geometric properties (such as offsetWidth) after styles have been invalidated by a change to the DOM state. This can result in poor performance. Learn more about forced reflows and possible mitigations." Se pone en rojo cuando los scripts fuerzan 30 ms o más de trabajo de layout de una sola vez durante la carga de la página.
El informe de abajo procede de una página de listado de cabañas que construí como si fuera un sitio de alquiler vacacional en WordPress, donde el script del tema recorta cada descripción a tres líneas y hace que todas las tarjetas tengan la misma altura.

Lighthouse 13.5 en móvil, throttling de DevTools (Slow 4G, 4x CPU).
- Top function call: la función exterior que inició el trabajo de layout. Pasa el cursor sobre la ruta acortada del archivo para ver la completa, que indica el tema, plugin o app. Un archivo en otro dominio suele ser una etiqueta de terceros, como un grabador de sesiones (Scripts que no puedes cambiar).
- Source: cada línea de código que pidió un tamaño.
- Total reflow time: el tiempo de layout que causó esa función o línea.
- Unscored: el insight no tiene peso en la puntuación de Performance. Las long tasks que tiene detrás (tramos de más de 50 ms en los que la página no puede responder) bajan la puntuación a través de Total Blocking Time.
La fila superior (388 ms) es menor que la suma de las filas de Source (743 ms) porque el script de las cabañas se ejecuta dos veces durante la carga, y la fila superior solo muestra la ejecución más pesada.
¿Por qué un script fuerza al navegador a rehacer el layout?
En el listado de cabañas anterior, una pulsación en el filtro «All» congela la página durante un tercio de segundo antes de que aparezcan las 48 tarjetas.
El layout es el paso donde el navegador calcula dónde va cada caja y qué tamaño tiene. Normalmente lo hace una vez por frame, después de ejecutar los scripts. Si un script modifica la página (un texto, una clase, una altura) y luego lee un tamaño, como offsetHeight, getBoundingClientRect(), scrollTop o getComputedStyle(), el navegador tiene que hacer el layout de inmediato, dentro del script, para poder responder. Paul Irish mantiene la lista completa de las lecturas que lo fuerzan.
Un único forced reflow suele ser trabajo de layout que el navegador habría hecho un instante después. En un bucle que modifica y vuelve a leer, se produce un layout completo en cada iteración: layout thrashing. El tema de las cabañas recorta cada descripción así:
while (el.offsetHeight > max && words.length > 1) {
words.pop();
el.textContent = words.join(' ') + '…';
} Cada palabra que elimina modifica el texto, por lo que la siguiente lectura de offsetHeight fuerza un nuevo layout: uno por cada palabra, en cada tarjeta.
¿Cuánto ralentiza la página un forced reflow?
Medí el listado de cabañas con el script del tema (Antes) y con CSS recortando el texto e igualando la altura de las tarjetas (Después). Ambas versiones tienen el mismo aspecto.
| Antes | Después | |
|---|---|---|
| Puntuación Lighthouse | 84 | 92 |
| Total Blocking Time | 360 ms | 0 ms |
| Total reflow time | 743 ms | ninguno |
| Una pulsación en «All» | 320 ms | 16 ms |

Antes

Después
Lighthouse 13.5 en móvil, throttling de DevTools.
Los 8 puntos proceden de Total Blocking Time: los layouts forzados mantienen la página bloqueada durante más tiempo. La pulsación es lo que percibe el visitante, y cuenta para el INP (Interaction to Next Paint), el Core Web Vital que mide la rapidez con la que una página responde a toques y clics. 320 ms supera el límite de 200 ms para un buen INP, y 298 ms correspondieron al layout que forzó el script; 16 ms es un único frame. Lighthouse nunca pulsa, así que medí las pulsaciones en Chrome con la CPU ralentizada a la velocidad de un móvil.
¿Cuándo es inofensivo un forced reflow?
- Una fila pequeña durante la carga. Una fila de algo más de 30 ms suele ser el primer layout de la página, adelantado un poco porque un script pidió un tamaño. El script del tema del listado de cabañas, reescrito para leer primero todas las alturas, mostró una fila de 37 ms en una de cada cuatro ejecuciones de Lighthouse, y la pulsación en «All» tardó 16 ms.
- Una fila «[unattributed]». No indica ninguna línea de código: vuelve a ejecutar Lighthouse y, si la fila desaparece, déjalo estar.
- Un script que inicia una animación. Los modales de Bootstrap y algunos plugins de popups leen un tamaño a propósito para que se inicie su transición CSS.

Lighthouse 13.5 en móvil, throttling de DevTools.
Corrígelo cuando el total reflow time sea de cientos de milisegundos, cuando un archivo tenga muchas filas o cuando el script también se ejecute con las pulsaciones: un filtro, un menú, un slider.
¿Qué script causa el forced reflow al pulsar?
Lighthouse solo carga la página, así que graba en el panel Performance las pulsaciones que no responden:
- Abre DevTools (F12), ve a Performance y ajusta la CPU a 4x slowdown para que el portátil se comporte como un móvil.
- Haz clic en Record, pulsa en el elemento que notes lento y detén la grabación.
- Abre Forced reflow en la barra lateral Insights. "Call frames that trigger reflow" indica la función y la línea.

Una pulsación en «All» en el listado de cabañas: clampText en cabins.js.
La consola muestra lo mismo como "[Violation] Forced reflow while executing JavaScript took 38ms" en cuanto activas Verbose en sus niveles de registro.
Si Lighthouse solo indica jquery.min.js, jQuery se encargó de la lectura para un tema o un plugin. Graba una recarga en el panel Performance y haz clic en un bloque de Layout con un triángulo rojo: en la pestaña Summary, dentro de Function stack, el archivo situado entre las líneas de jQuery (con una ruta como /wp-content/plugins/<name>/) es el plugin.
Los field data registran las pulsaciones y los clics de los visitantes reales. En CoreDash, abre INP, ve a Breakdown y agrupa por LOAF: Chrome registra qué scripts se ejecutaron en cada frame lento (Long Animation Frames), de modo que cada fila es un script con el INP de las interacciones en las que intervino. Un forced reflow dentro de un handler de clic o pulsación cuenta como processing, la parte morada de cada barra; empieza por un script lento cuya parte morada sea extensa.

INP por script en CoreDash, desktop, p75: el verde es input delay, el morado processing y el azul presentation.
¿Cómo se soluciona un forced reflow?
Empieza por el archivo que aparece en Top function call y evalúa si CSS puede hacer el trabajo de ese script. Un texto recortado a tres líneas (line-clamp), tarjetas de igual altura (grid), una cabecera fija arriba (position: sticky) o un contenedor de imagen que mantenga su proporción (aspect-ratio) no necesitan ningún script; y sin el script, nada fuerza el layout.
Tras aplicar cualquiera de los fixes siguientes, vuelve a grabar la misma pulsación: Forced reflow debería haber desaparecido de la barra lateral Insights. Después, comprueba el resultado en Lighthouse y en los field data; Search Console tarda hasta 28 días.
Temas, plugins y page builders de WordPress
La ruta en Top function call indica el tema (/wp-content/themes/) o el plugin (/wp-content/plugins/). Revisa primero los ajustes. La opción «Stretch Section» de Elementor ensancha una sección mediante JavaScript en cada carga y resize; el diseño de página Elementor Full Width con un contenedor de ancho completo lo hace mediante CSS. Un slider Swiper configurado con «auto» en slides per view mide cada diapositiva; con un número fijo calcula los anchos sin tener que leerlos.
Retrasar JavaScript en WP Rocket o en un plugin similar elimina el aviso, pero el script se ejecutará con la primera pulsación o scroll del visitante, provocando los mismos layouts forzados. Es mejor corregir el script, o reportar el problema al desarrollador del plugin y sustituirlo si no lo soluciona.
Si el tema recorta texto con JavaScript, hazlo con CSS en el child theme usando la clase de texto de tu tema:
.cabin .desc {
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 3;
line-clamp: 3;
overflow: hidden;
} Luego desactiva el script de recorte del tema: mediante una opción del propio tema o con wp_dequeue_script() usando su handle.
Temas y apps de Shopify
En el editor del tema, dentro de App embeds, desactiva las apps una a una y vuelve a ejecutar Lighthouse. Cuando el aviso desaparezca, reporta la app a su desarrollador o sustitúyela. Una ruta con /assets/ en Top function call corresponde a un archivo del tema, ya sea del propio tema o de una app que le añadió código. Dawn y los temas basados en él suelen leer el tamaño una sola vez y agrupar sus cambios en lotes; por tanto, revisa primero las apps.
Tu propio JavaScript y React
Cuando un script tenga que medir, lee primero todos los tamaños y aplica los cambios después, de modo que un único layout sirva para todas las lecturas. Por ejemplo, en un acordeón que ajusta cada panel a su propia altura:
const panels = [...document.querySelectorAll('.panel')];
// A layout for every panel: read, write, read, write
panels.forEach(p => { p.style.maxHeight = p.scrollHeight + 'px'; });
// One layout: read every height, then write them all
const heights = panels.map(p => p.scrollHeight);
panels.forEach((p, i) => { p.style.maxHeight = heights[i] + 'px'; }); - Resize y scroll: ResizeObserver e IntersectionObserver te proporcionan los tamaños a partir del layout que el navegador acaba de hacer. Úsalos en lugar de leer tamaños en un handler de resize o scroll.
- React: mide una sola vez en
useLayoutEffecty actualiza el estado una sola vez. Monta los menús, popovers y tooltips solo cuando se abran; de ese modo, los elementos cerrados no medirán nada durante la carga.
Scripts que no puedes cambiar
Los grabadores de sesiones y las etiquetas de mapas de calor leen tamaños a propósito: Microsoft Clarity lee una docena de tamaños de página al iniciarse y la posición de cada elemento en el que hace clic el visitante. Elimina la etiqueta, graba un muestreo de visitas o cárgala únicamente en las páginas que estés analizando.
¿Tienes un forced reflow en un plugin o app que no puedes sustituir, o pulsaciones que se bloquean en el móvil de tus visitantes y no en el tuyo? En una Core Web Vitals power hour encontramos el script responsable de tus pulsaciones más lentas y decidimos juntos si conviene corregirlo, sustituirlo o eliminarlo.
Entérate de qué va lento de verdad.
Trazo tu critical rendering path con datos reales. Te paso una lista de fixes priorizada. No otro informe de Lighthouse.
Quiero la auditoría