wandres.dev
OPTIMIZAR INP · La interacción que responde

Las tres partes del INP: convertir una métrica opaca en tres tareas

El desglose de una interacción en retraso de entrada, duración del procesamiento y retraso de presentación, cómo medir cada parte por separado en campo, y qué familia de optimizaciones corresponde a cada una.

⏱ 19 min

«El INP está en 480 milisegundos» es un dato que no dice qué hacer. La métrica se vuelve accionable en el momento en que se parte en sus tres componentes, porque cada uno tiene causas distintas, herramientas de diagnóstico distintas y soluciones distintas. Un INP de 480 con 400 de retraso de entrada y uno de 480 con 400 de retraso de presentación son dos problemas que no comparten ni una sola línea de solución.

🎯 Al terminar esta lección sabrás
  • Definir los tres componentes y el momento exacto en que empieza y acaba cada uno.
  • Medir las tres partes por separado con la instrumentación de campo.
  • Asignar cada componente a su familia de optimizaciones.
  • Interpretar el reparto típico y saber cuándo el tuyo es anómalo.

Los tres componentes

Una interacción, en el modelo del INP, empieza cuando el usuario actúa y acaba cuando el navegador presenta el siguiente fotograma que refleja el resultado. Ese intervalo se divide en tres.

Retraso de entrada. Desde que el usuario actúa hasta que empieza a ejecutarse el primer manejador del evento. Es tiempo de espera puro: el hilo principal estaba ocupado con otra cosa.

Duración del procesamiento. Desde que empieza el primer manejador hasta que termina el último. Es el tiempo de tu código: validaciones, actualizaciones de estado, escrituras en el DOM, llamadas a bibliotecas, analítica.

Retraso de presentación. Desde que termina el último manejador hasta que el fotograma con el resultado está en pantalla. Incluye el recálculo de estilo, el layout, la pintura, la composición, y cualquier trabajo que se haya colado en medio.

flowchart TB
U[El usuario pulsa] --> D[Retraso de entrada]
D --> P[Duracion del procesamiento]
P --> R[Retraso de presentacion]
R --> F[Fotograma en pantalla]
D -.->|causa| DC[El hilo estaba ocupado con otra tarea]
P -.->|causa| PC[Los manejadores hacen demasiado trabajo]
R -.->|causa| RC[Estilo layout y pintura del cambio]
style D fill:#f38ba8,color:#11111b
style P fill:#f9e2af,color:#11111b
style R fill:#cba6f7,color:#11111b
style F fill:#a6e3a1,color:#11111b
style DC fill:#89b4fa,color:#11111b
style PC fill:#89b4fa,color:#11111b
style RC fill:#89b4fa,color:#11111b

Los umbrales de la métrica, para tener la referencia: por debajo de 200 milisegundos es bueno, por encima de 500 es malo, medido en el percentil 75 de las interacciones de la sesión. Y el INP de una página no es la media de sus interacciones: es aproximadamente la peor, con una corrección que descarta los valores extremos en sesiones con muchas interacciones. Eso significa que una sola interacción horrible arruina la métrica de toda la sesión aunque las otras cincuenta sean impecables.

Medir las tres partes en campo

La librería oficial de métricas web entrega el desglose completo en el atributo del evento. Este es el código que hay que tener en producción:

import { onINP } from 'web-vitals/attribution';

onINP(({ value, rating, attribution: a }) => {
  const partes = {
    entradaMs: Math.round(a.inputDelay),
    procesamientoMs: Math.round(a.processingDuration),
    presentacionMs: Math.round(a.presentationDelay),
  };
  navigator.sendBeacon('/telemetria/inp', JSON.stringify({
    valor: Math.round(value),
    calificacion: rating,                    // 'good' | 'needs-improvement' | 'poor'
    ...partes,
    tipo: a.interactionType,                 // 'pointer' | 'keyboard'
    objetivo: a.interactionTarget,           // selector del elemento
    ruta: location.pathname,
    memoriaGB: navigator.deviceMemory ?? null,
    nucleos: navigator.hardwareConcurrency ?? null,
  }));
}, { reportAllChanges: false });

interactionTarget es el campo que convierte esto en accionable: un selector del elemento con el que el usuario interactuó. Con eso, el panel deja de decir «el INP es malo» y pasa a decir «el botón de filtro de la página de listado tiene 620 milisegundos, repartidos en 80 de entrada, 380 de procesamiento y 160 de presentación».

Para el diagnóstico local, el panel de rendimiento del navegador muestra la pista de interacciones con el mismo desglose visual, alineado con el flame chart. Grabar mientras haces la interacción problemática y mirar esa pista es el camino más rápido de un número a una función concreta.

Y una alternativa sin dependencias, útil para instrumentar rápido, usando directamente la API de eventos:

new PerformanceObserver((lista) => {
  for (const e of lista.getEntries()) {
    if (!e.interactionId) continue;          // solo interacciones reales
    console.log({
      tipo: e.name,
      entradaMs: Math.round(e.processingStart - e.startTime),
      procesamientoMs: Math.round(e.processingEnd - e.processingStart),
      presentacionMs: Math.round(e.startTime + e.duration - e.processingEnd),
      totalMs: Math.round(e.duration),
    });
  }
}).observe({ type: 'event', durationThreshold: 40, buffered: true });

durationThreshold fija el mínimo a partir del cual se reportan eventos; el valor mínimo admitido es 16 y el valor por defecto es 104. Bajarlo a 40 da más visibilidad a costa de más entradas.

Qué optimización corresponde a cada parte

Aquí está el valor de todo el desglose. Cada componente tiene su familia y no se cruzan.

Componente Causas Familia de soluciones
Retraso de entrada Tareas largas en curso, trabajo periódico, hidratación, scripts de terceros Trocear con cesión, diferir el arranque, mover trabajo a un trabajador, controlar terceros
Duración del procesamiento Manejadores que hacen demasiado, oyentes múltiples, layout forzado dentro del manejador Separar respuesta visual de trabajo pesado, ceder antes de lo no urgente, agrupar lecturas y escrituras
Retraso de presentación DOM grande, layout costoso, animaciones, trabajo colado tras el manejador Reducir el alcance del cambio, contención de renderizado, virtualización, menos nodos

La asignación es estricta y evita mucho trabajo inútil. Trocear un bucle no mejora el retraso de presentación. Reducir el número de nodos del DOM no mejora el retraso de entrada. Y ambas cosas se intentan constantemente sobre el problema equivocado porque nadie miró el reparto.

El reparto típico y cuándo el tuyo es raro

En los datos agregados que publican los equipos de navegadores y las herramientas de medición de usuarios reales, el reparto típico pone la duración del procesamiento y el retraso de presentación por delante del retraso de entrada, con los dos primeros llevándose la mayor parte del tiempo y el retraso de entrada quedando en una fracción menor.

Eso da tres perfiles anómalos que merece la pena reconocer.

Retraso de entrada dominante. El hilo está ocupado cuando el usuario actúa. Si ocurre sobre todo en los primeros segundos, es el arranque: hidratación, evaluación de bundle. Si ocurre en cualquier momento, hay trabajo periódico o de terceros. Es el perfil que menos aparece y el más fácil de arreglar cuando aparece.

Procesamiento dominante y muy variable. Tus manejadores. La variabilidad entre interacciones apunta a que el coste depende de los datos: un filtro sobre una lista que a veces tiene 30 elementos y a veces 3.000.

Presentación dominante. El cambio que provocas es caro de renderizar. Sospechosos: un DOM enorme, un cambio que invalida el layout de toda la página, una transición que arranca justo ahí. Es el perfil más común en aplicaciones maduras y el que más se ignora, porque la gente asume que el problema siempre está en el JavaScript.

El INP mide la peor interacción, no la media, y eso cambia por completo la estrategia

Este es el punto que hace que las estrategias intuitivas de optimización de INP fallen.

El INP de una sesión con menos de cincuenta interacciones es la peor de todas. Con más interacciones, se descarta aproximadamente una interacción mala por cada cincuenta, lo cual sigue siendo una medida de cola alta, no de tendencia central. Y después, el INP del sitio es el percentil 75 de esos valores por sesión.

Las consecuencias prácticas son tres y ninguna es obvia.

Uno: mejorar la interacción más frecuente puede no mover la métrica en absoluto. Si el clic en el menú se usa cien veces por sesión y tarda 90 milisegundos, y la apertura del filtro avanzado se usa una vez y tarda 700, el INP de esa sesión es 700. Optimizar el menú de 90 a 40 no cambia nada. Hay que atacar la interacción más lenta, aunque sea rara.

Dos: las interacciones raras y caras son las que más cuesta encontrar. No aparecen en las pruebas de laboratorio porque nadie piensa en probarlas, y en el perfil local con datos de prueba son rápidas. La única forma de encontrarlas es la instrumentación de campo con interactionTarget, ordenando por valor máximo y no por frecuencia.

Tres, y es contraintuitivo: reducir el número de interacciones puede empeorar el INP. Si simplificas un flujo de diez clics a tres, y uno de esos tres era el lento, ahora el lento representa un tercio de las interacciones en lugar de un décimo, y la corrección estadística que descartaba valores extremos deja de aplicarse. Has mejorado la experiencia y empeorado la métrica.

Ese tercer punto lleva a una recomendación de método: no persigas el número, persigue la lista de interacciones lentas. Construye un panel que muestre, por selector de destino, el máximo y el percentil 95 de cada tipo de interacción. Esa lista es la que se trabaja de arriba abajo, y la métrica agregada mejora como consecuencia.

Y un detalle técnico que afecta a la medición y que conviene conocer: una interacción puede agrupar varios eventos. Un clic genera pointerdown, pointerup y click, y todos comparten el mismo interactionId. La duración de la interacción es la del evento más largo del grupo, no la suma. Por eso a veces el desglose no cuadra con lo que ves en el flame chart: estás mirando un evento del grupo y la métrica reporta otro. Con el teclado ocurre lo mismo con keydown, keypress y keyup.

El plan de trabajo

Con el desglose en la mano, el orden es este.

Uno: instrumenta con atribución y deja pasar una semana. Sin datos de campo por selector, todo lo demás es adivinar.

Dos: ordena por valor máximo y coge las tres peores. No las más frecuentes.

Tres: para cada una, mira el reparto entre las tres partes y ve a la familia de soluciones que corresponda. Las tres lecciones siguientes de este nivel desarrollan cada familia.

Cuatro: mide después de cada cambio en campo, no solo en laboratorio. El INP es una métrica de campo y las mejoras se confirman ahí.

⚔️ Reto práctico

Instrumenta el INP con atribución en tu producto y recoge datos durante una semana. Construye la tabla de las diez interacciones con mayor valor máximo, con el desglose de las tres partes en cada una. Después escoge la peor y reproduce la interacción en el panel de rendimiento con la CPU estrangulada: comprueba si el reparto que ves en el laboratorio coincide con el de campo. Cuando no coincide, casi siempre es porque en campo hay terceros que en local no cargan.