Por qué se sustituyó el FID
Durante años optimizamos el First Input Delay. El problema del FID es que solo miraba la primera interacción de la página, y solo la parte previa, el momento en que el navegador esperaba para empezar. En cuanto tu código arrancaba, el FID dejaba de contar. Una página podía sacar una nota perfecta en FID y aun así sentirse lenta de usar.
El INP, Interaction to Next Paint, arregla eso. Mide cada clic, toque y pulsación de teclado en la página, y cuenta el viaje completo: desde que el usuario toca la pantalla hasta que el navegador muestra un nuevo frame. Si haces clic en un menú y tarda 500ms en abrirse porque el hilo principal está ocupado, tu INP lo refleja.
Qué pasa durante una interacción
Cuando un usuario hace clic en algo, ocurren tres cosas en orden:
- Input delay: la espera antes de que tu código pueda siquiera empezar. El hilo principal está ocupado con otra cosa (JavaScript pesado, hidratación, un script de terceros) y el clic queda en cola.
- Processing time: se ejecuta tu event handler. Esto es tu código: una actualización de estado, un fetch, un cálculo.
- Presentation delay: el navegador recalcula el layout, pinta y muestra el nuevo frame.
El INP es la suma de los tres. Si cualquiera va lento, la interacción se nota lenta.
Ceder el hilo principal
La regla principal para tener un buen INP es simple: no bloquees el hilo principal mucho tiempo. Si tienes un trozo de trabajo que tarda 200ms, el navegador no puede responder a clics durante esos 200ms.
El truco clásico era setTimeout(fn, 0) para partir una tarea larga en trozos. Funciona, pero manda tu tarea al final de una cola larga, y otras cosas se cuelan por delante.
scheduler.yield() es la nueva API nativa para esto. Devuelve el control al navegador para que pinte o atienda un clic pendiente, y luego retoma tu código justo después. Está pensada exactamente para este caso.
async function processLargeBatch() {
for (const item of giantList) {
calculate(item);
// Cada cierto tiempo, deja respirar al navegador
if (shouldYield()) {
await scheduler.yield();
}
}
}
Úsalo dentro de bucles que procesan muchos elementos, o en cualquier sitio donde tengas un bloque de trabajo síncrono que no necesita acabar de una sola vez.
Lighthouse te va a mentir sobre el INP
El INP es una métrica de campo. Solo tiene sentido con usuarios reales en dispositivos reales. Lighthouse corre en un entorno de laboratorio controlado, así que una página puede sacar 100 en Lighthouse y tener un INP horrible para los visitantes reales con un Android de gama media.
La solución es medir en producción. La librería web-vitals tiene una build de attribution que te dice no solo el valor del INP, sino qué interacción lo causó y dónde se fue el tiempo.
import { onINP } from 'web-vitals/attribution';
onINP((attribution) => {
console.log('Interaction:', attribution.interactionType); // 'pointerdown', 'keydown'
console.log('Target Element:', attribution.processedEventTarget);
});
Manda esto a tu sistema de analítica. Una vez puedes ver qué botón en qué página va lento, el arreglo suele ser obvio.
Consejos prácticos
Evita las tareas largas. Cualquier cosa que pase de 50ms en el hilo principal es un problema. Aplaza el trabajo que no tiene que pasar ahora mismo: analítica, prefetching, hidratación no crítica. Vigila la hidratación en particular, es una causa habitual de mal INP en la primera interacción tras cargar una página. Si tienes cómputo pesado, mira WebAssembly para moverlo fuera del hilo principal, y los patrones de programación eficiente para mantener pequeño el trabajo total.
Si quieres una herramienta que detecte este tipo de problemas automáticamente, nuestro SEO Expert incluye diagnósticos de INP.