Cómo mejorar el INP: qué retrasa la respuesta al clic
El INP suma retraso de entrada, procesamiento y presentación. Cómo encontrar la interacción lenta y reducir cada fase, con scheduler.yield y LoAF.
En esta página 8 min de lectura
- Qué mide el INP
- Las tres fases de una interacción
- Tareas largas: el origen de la mayoría de los problemas
- Cómo encontrar la interacción lenta
- Cómo reducir el retraso de entrada
- Cómo reducir el tiempo de procesamiento
- Cómo reducir el retraso de presentación
- Laboratorio y campo dan respuestas distintas
- Cómo revisar tu sitio
El INP (Interaction to Next Paint) mide cuánto tarda la página en mostrar una respuesta visual después de un clic, un toque o una tecla. Observa las interacciones de toda la visita y reporta la más lenta (en páginas con muchas interacciones, ignora la mayor de cada 50). Es bueno hasta 200 ms y malo por encima de 500 ms, en el percentil 75 de las visitas (web.dev). Para reducirlo, averigua qué interacción es lenta, mira en cuál de las tres fases está el tiempo (espera, ejecución del código o pintado) y corrige esa fase.
Este texto sigue las guías de web.dev sobre optimización del INP (actualizada en septiembre de 2025) y sobre tareas largas. Para el contexto de las tres Core Web Vitals, consulta la guía de Core Web Vitals.
Qué mide el INP
| Rango | INP en el percentil 75 |
|---|---|
| Bueno | hasta 200 ms |
| Necesita mejorar | más de 200 ms y hasta 500 ms |
| Malo | más de 500 ms |
La evaluación se hace por separado para móvil y escritorio. La interacción empieza con el gesto del usuario y termina cuando el navegador pinta el siguiente fotograma en pantalla. Eso significa que el INP no mide cuándo termina tu código, sino cuándo el usuario ve que algo cambia. Cuando la página vuelve desde la caché de avance y retroceso (bfcache), el INP empieza de cero, porque para el usuario es una visita nueva (web.dev).
Las tres fases de una interacción
| Fase | Qué es | Causa habitual |
|---|---|---|
| Retraso de entrada (input delay) | Desde el gesto hasta que el primer manejador de eventos empieza a ejecutarse. | El hilo principal estaba ocupado con otra cosa: scripts cargando, temporizadores, otra interacción. |
| Tiempo de procesamiento (processing duration) | Lo que tardan en ejecutarse los manejadores de eventos. | Demasiado código dentro del click o del input. |
| Retraso de presentación (presentation delay) | Desde el final de los manejadores hasta que el fotograma aparece en pantalla. | Recálculo de estilos, layout y pintado costosos, DOM grande, mucho HTML generado con JavaScript. |
Las tres fases se miden sumando todos los eventos del mismo fotograma (por ejemplo pointerup, mouseup y click de un único clic), y así es como lo calcula la biblioteca web-vitals (web.dev).
Tareas largas: el origen de la mayoría de los problemas
El navegador ejecuta casi todo en un único hilo principal: interpretar HTML y CSS, ejecutar JavaScript, calcular el layout y pintar. Cada bloque de trabajo es una tarea. Una tarea de más de 50 ms se llama tarea larga (web.dev). Mientras se ejecuta, un clic queda en cola. Si la tarea está en marcha cuando el usuario hace clic, aumenta el retraso de entrada. Si la tarea es el propio manejador del clic, aumenta el tiempo de procesamiento. El remedio en los dos casos es el mismo: partir el trabajo en trozos más pequeños y devolver el turno al navegador entre ellos.
Cómo encontrar la interacción lenta
Saber que el INP está alto no dice qué botón es el problema. El camino que recomienda web.dev va del campo al laboratorio (web.dev):
Revisa el CrUX en PageSpeed Insights o en Search Console. Te dice si hay un problema de INP en móvil y en escritorio, pero no qué interacción lo causa.
Recoge datos de usuarios reales con la biblioteca web-vitals en su versión de atribución. Indica el elemento pulsado, el tipo de interacción y el tiempo de cada fase.
Usa los datos de Long Animation Frames (LoAF) para llegar al script responsable.
Reproduce la interacción en Chrome DevTools para investigar y probar la corrección.
Con la biblioteca web-vitals
import {onINP} from 'web-vitals/attribution';
onINP(({value, attribution}) => {
console.log('INP', value);
console.log('Elemento', attribution.interactionTarget);
console.log('Tipo', attribution.interactionType); // 'pointer' o 'keyboard'
console.log('Retraso de entrada', attribution.inputDelay);
console.log('Procesamiento', attribution.processingDuration);
console.log('Presentación', attribution.presentationDelay);
console.log('Estado de la página', attribution.loadState);
console.log('Script más largo', attribution.longestScript);
});El campo loadState muestra si la interacción ocurrió con la página todavía cargando, situación en la que el retraso de entrada suele ser mayor. longestScript trae el script más largo que coincidió con la interacción y en qué fase se ejecutó. Estos campos que dependen de LoAF solo vienen rellenos en los navegadores que admiten la API (GitHub GoogleChrome/web-vitals).
Con la API Long Animation Frames
Un long animation frame es un fotograma cuya actualización visual se retrasó más de 50 ms. Cada registro lista los scripts de más de 5 ms que se ejecutaron en el fotograma, con la URL del archivo, el nombre de la función, lo que llamó al script (un listener de eventos, un requestAnimationFrame, un temporizador) y cuánto tiempo se gastó en layout forzado (Chrome for Developers). Con la URL del script puedes separar tu código del código de terceros.
const observer = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
console.log(entry.duration, entry.blockingDuration, entry.scripts);
}
});
observer.observe({type: 'long-animation-frame', buffered: true});LoAF existe en Chrome y en Edge desde la versión 123. Firefox y Safari no la admiten (MDN browser-compat-data). Los scripts de iframes de otro origen, los workers y las extensiones no aparecen en la atribución.
Con Chrome DevTools
El panel Performance tiene una vista de métricas en directo. Al interactuar con la página, registra el INP local y lista cada interacción con el elemento, el tiempo y las tres fases (Chrome for Developers). Para investigar una interacción concreta:
Activa la limitación de CPU, o usa un Android de gama baja o media con depuración remota. Las máquinas rápidas esconden las tareas largas.
Pulsa grabar, haz la interacción y detén la grabación.
En la pista Interactions, la interacción aparece encima de la actividad del hilo principal. El bigote de la izquierda es el retraso de entrada, el bloque sólido es el procesamiento y el bigote de la derecha es la presentación.
Prueba también durante la carga, en cuanto aparece la página, cuando el hilo principal suele estar más ocupado.
Este procedimiento es el de web.dev para el diagnóstico manual en laboratorio.
Cómo reducir el retraso de entrada
El retraso de entrada es tiempo en el que tu manejador ni siquiera ha empezado. La causa es otra cosa que ocupa el hilo principal en el momento del clic:
La evaluación de scripts durante la carga. Interpretar, compilar y ejecutar JavaScript genera tareas largas justo cuando el usuario suele interactuar por primera vez. Menos JavaScript en la carga inicial reduce ese riesgo.
Temporizadores (
setInterval,setTimeout) que ejecutan trabajo pesado a intervalos.Interacciones superpuestas: el procesamiento de una tecla retrasa la siguiente.
Scripts de terceros (chat, analítica, anuncios, pruebas A/B) que se ejecutan en el mismo hilo. Los campos
sourceURLeinvokerde LoAF muestran cuáles son. Para cada uno, evalúa si aporta un valor que justifique el coste; si se queda, cárgalo conasyncodefer, o más tarde (web.dev).
Cómo reducir el tiempo de procesamiento
La regla de web.dev es hacer lo mínimo posible dentro del manejador de eventos: actualizar primero la interfaz y aplazar el resto.
Actualiza la pantalla y aplaza lo demás
campo.addEventListener('input', (evento) => {
// Lo que el usuario necesita ver ahora
actualizarCampo(evento);
// El resto queda para después del siguiente fotograma
requestAnimationFrame(() => {
setTimeout(() => {
contarPalabras();
revisarOrtografia();
guardarBorrador();
}, 0);
});
});Este patrón, adaptado del ejemplo de web.dev, funciona en todos los navegadores. requestAnimationFrame espera al siguiente fotograma y setTimeout pasa el trabajo a una tarea nueva, después del pintado.
Cede el turno en trabajos largos con scheduler.yield
Cuando el trabajo es una secuencia de pasos, como procesar una lista, puedes devolver el turno al navegador entre un paso y otro. scheduler.yield() pausa la tarea, deja que el navegador atienda clics y pinte, y reanuda tu código con prioridad sobre otras tareas de la cola (MDN). Según los datos de compatibilidad de MDN, existe en Chrome y Edge 129 y en Firefox 142; Safari todavía no lo admite (MDN browser-compat-data, consultado en octubre de 2026). Por eso conviene usarlo con una alternativa:
function cederElTurno() {
if (globalThis.scheduler?.yield) {
return scheduler.yield();
}
// Alternativa para navegadores sin scheduler.yield
return new Promise((resolve) => setTimeout(resolve, 0));
}
async function procesarElementos(elementos) {
for (const elemento of elementos) {
procesar(elemento);
await cederElTurno();
}
}La función sigue el modelo de la guía de tareas largas de web.dev. La diferencia de la alternativa: con setTimeout la continuación va al final de la cola, y otros scripts (incluidos los de terceros) pueden ejecutarse antes. El código sigue respondiendo, pero sin la prioridad al reanudarse. Existe también el paquete scheduler-polyfill, citado en la misma guía.
Evita el layout forzado
Cambiar un estilo y justo después leer una medida, como offsetHeight, obliga al navegador a recalcular el layout en medio de tu código. Dentro de un bucle, eso multiplica el coste. Agrupa las lecturas antes de las escrituras. En LoAF, ese tiempo aparece en forcedStyleAndLayoutDuration.
Cómo reducir el retraso de presentación
Cuando tu código termina, el navegador todavía tiene que recalcular estilos, hacer el layout y pintar. Ese coste crece con el tamaño de la página:
DOM grande. Lighthouse avisa por encima de 800 nodos y lo marca como excesivo por encima de 1400 (web.dev). Elimina contenedores innecesarios e inserta el contenido oculto (pestañas, menús) solo cuando el usuario lo pida.
Contenido fuera de la pantalla renderizado sin necesidad. La propiedad CSS
content-visibility: autoaplaza el renderizado de las secciones que no están visibles. Funciona desde Chrome 85, Firefox 125 y Safari 18 (MDN browser-compat-data).Mucho HTML generado con JavaScript de golpe. El navegador solo pinta después de interpretarlo y renderizarlo todo. Genéralo por partes o entrégalo listo desde el servidor.
.seccion-pie {
content-visibility: auto;
contain-intrinsic-size: auto 600px; /* altura estimada, ajústala a tu layout */
}Laboratorio y campo dan respuestas distintas
Lighthouse no mide el INP, porque no hace clic en nada. Reporta el TBT (Total Blocking Time), una métrica de carga que se correlaciona con el INP pero no lo sustituye (web.dev). Una buena nota en Lighthouse no garantiza un buen INP, y las interacciones posteriores a la carga solo aparecen si pruebas el flujo a mano.
En el campo también hay diferencias de cobertura. El CrUX procede solo de usuarios de Chrome que aceptaron compartir estadísticas, en escritorio y en Android (Chrome for Developers). La API de Event Timing, que web-vitals usa para calcular el INP, existe en Chrome, en Firefox 89 y en Safari 26.2 en adelante, pero LoAF solo en Chrome y Edge. En la práctica, tu RUM mide el INP en más navegadores, pero la atribución detallada por script solo llega de los navegadores Chromium. La comparación entre estas fuentes está en Lighthouse, CrUX y RUM.
Corregir una interacción lenta suele dejar al descubierto la siguiente. web.dev trata la optimización del INP como un ciclo: medir en el campo, reproducir en el laboratorio, corregir y volver a medir. Los problemas de LCP y CLS tienen guías propias: cómo mejorar el LCP y cómo corregir el CLS.
Cómo revisar tu sitio
En WebVyze, el diagnóstico de INP usa Google PageSpeed para mostrar la situación de la página, y el test de Core Web Vitals muestra el INP del CrUX de los últimos 28 días, en móvil y escritorio, para hasta 10 URL. Las dos herramientas son gratuitas. En los planes de pago, un script de visitantes reales basado en la biblioteca web-vitals registra el INP de cada visita con su causa.
Preguntas frecuentes
- ¿Desplazarse por la página o pasar el ratón cuenta para el INP?
- No. El INP observa clics, toques y teclas. Pasar el ratón, desplazarse y hacer zoom quedan fuera.
- ¿Y si el visitante no hace clic en nada?
- En ese caso la visita no tiene valor de INP. Lo mismo vale para quien solo se desplazó por la página y para los robots que no simulan interacción.
- ¿Cuentan las interacciones dentro de iframes?
- Cuentan para el INP del CrUX. En cambio, las API del navegador, y por tanto la biblioteca web-vitals, no ven el contenido de los iframes, así que tu RUM puede mostrar un valor distinto al del CrUX en páginas con iframes.
- ¿Sigue sirviendo isInputPending para decidir cuándo ceder el turno?
- web.dev ya no recomienda esa API. Puede responder mal justo después de una interacción y no cubre las actualizaciones visuales que también necesitan espacio. La recomendación actual es scheduler.yield o scheduler.postTask.
Vea lo que su sitio necesita corregir.
Una verificación gratuita por ley, con la causa y la guía de corrección de cada problema.
Siga leyendo
- SEO técnico
Bots de IA en robots.txt: búsqueda, acceso del usuario y entrenamiento
GPTBot, ClaudeBot, OAI-SearchBot, Google-Extended: qué hace cada robot de IA, si respeta el robots.txt y cómo bloquear el entrenamiento sin dejar la búsqueda.
- SEO técnico
Redirecciones: cadenas, bucles y cómo corregirlos
Cómo trata Google los 301, 302, 307 y 308, qué causan las cadenas y los bucles en el rastreo y la velocidad, y cómo mapear, corregir y probar redirecciones.
- SEO técnico
llms.txt: qué es, qué no hace y cómo crearlo
El llms.txt resume el sitio para agentes de IA. Su formato, qué dicen Google y otras empresas, cuándo merece la pena crearlo y cómo escribir un buen archivo.