Frontend 21 jul 2026 · 5 min de lectura

INP: por qué tu app se siente lenta aunque cargue rápido

Yohangel Ramos

Yohangel Ramos

Tech Lead · Senior Fullstack Developer

Tienes el LCP en verde, el bundle está optimizado, Lighthouse te da 95 y aun así alguien del equipo dice "la app se siente lenta". No está loco. Está describiendo algo que tu métrica de carga no ve: lo que pasa cuando toca un botón y la interfaz tarda en reaccionar. Esa sensación tiene nombre y desde 2024 es un Core Web Vital: INP, Interaction to Next Paint. Mide el peor caso de latencia entre que el usuario interactúa y la pantalla responde. Un producto puede cargar rapidísimo y sentirse basura, porque cargar y responder son dos problemas distintos. Este es el que más me ha costado explicarle a gente que solo mira el número de carga.

Qué mide INP y por qué el LCP no lo cubre

El LCP (Largest Contentful Paint) responde a "¿cuánto tardó en aparecer el contenido principal?". Es un problema de arranque: sucede una vez, al principio. INP es otra cosa: durante toda la sesión, cada clic, cada tap, cada tecla dispara un ciclo de "el navegador recibe el evento → corre tu JavaScript → repinta". INP se queda con la peor de esas latencias y esa es tu nota.

La diferencia importa porque las apps modernas viven en la interacción, no en la carga. Un dashboard, un editor, un panel de filtros: el usuario carga una vez y luego interactúa mil. Si cada filtro que activa congela la interfaz 300ms, tu LCP perfecto no lo salva. El umbral bueno de INP es 200ms; por encima de 500ms se siente roto.

El culpable casi siempre es el mismo: tareas largas

El navegador es de un solo hilo para tu JavaScript. Cuando corre una función, no puede repintar ni atender otro evento hasta que esa función termina. Si al hacer clic disparas un cálculo o un render que tarda 250ms, durante esos 250ms la interfaz está muerta: el botón no muestra su estado activo, nada se mueve. Eso es una "tarea larga" y es el 90% de los problemas de INP que he depurado.

El patrón típico: un input de búsqueda que en cada tecla filtra y re-renderiza una lista de 2.000 elementos. Cada pulsación reconstruye el árbol entero antes de dejar que el navegador repinte el propio texto que estás escribiendo. El resultado es un input que va a tirones aunque el filtrado sea trivial.

// Mide dónde se va el tiempo: registra cualquier tarea que bloquee >50ms
new PerformanceObserver((list) => {
  for (const entry of list.getEntries()) {
    console.warn('Tarea larga:', Math.round(entry.duration), 'ms', entry);
  }
}).observe({ type: 'longtask', buffered: true });

Antes de optimizar nada, mido. La PerformanceObserver de longtask te dice qué interacciones bloquean el hilo y cuánto. No adivines: casi nunca es lo que crees.

Arreglarlo en React: separar lo urgente de lo que puede esperar

La clave conceptual es que no todo lo que dispara una interacción es igual de urgente. Cuando escribes en un buscador, mostrar la letra que tecleaste es urgentísimo; recalcular la lista filtrada puede esperar 50ms sin que nadie lo note. React 18 me dio las herramientas para decir exactamente eso.

useDeferredValue marca un valor como "de baja prioridad": React pinta primero la entrada urgente (el texto del input) y recalcula lo derivado después, sin bloquear.

function Buscador({ items }) {
  const [query, setQuery] = useState('');
  const diferido = useDeferredValue(query);

  // Se recalcula con el valor diferido, no con cada tecla
  const filtrados = useMemo(
    () => items.filter((i) => i.nombre.includes(diferido)),
    [items, diferido],
  );

  return (
    <>
      <input value={query} onChange={(e) => setQuery(e.target.value)} />
      <Lista items={filtrados} />
    </>
  );
}

El input responde instantáneo porque su render no espera al filtrado. Para acciones más pesadas —cambiar de pestaña, aplicar un filtro que reordena media pantalla— uso useTransition, que envuelve la actualización costosa y deja que el clic muestre su feedback antes de arrancar el trabajo:

const [pending, startTransition] = useTransition();

function onFiltrar(nuevo) {
  setFiltroActivo(nuevo);              // urgente: el botón se marca ya
  startTransition(() => {
    setResultados(recalcular(nuevo));  // no urgente: no bloquea el clic
  });
}

Cuando no hay React de por medio: cede el hilo

A veces el trabajo pesado no es un render, es un bucle: procesar un CSV, transformar un JSON grande. Ahí la solución es cortar la tarea larga en trozos y devolverle el control al navegador entre uno y otro, para que pueda atender clics y repintar. La forma moderna es scheduler.yield(); el fallback de siempre es un setTimeout(0).

async function procesar(filas) {
  for (let i = 0; i < filas.length; i++) {
    trabajar(filas[i]);
    if (i % 100 === 0) await yieldAlHilo(); // respira cada 100 filas
  }
}

const yieldAlHilo = () =>
  'scheduler' in window && 'yield' in scheduler
    ? scheduler.yield()
    : new Promise((r) => setTimeout(r, 0));

Si el trabajo es de verdad CPU-intensivo y no necesita el DOM, la respuesta correcta no es trocear sino sacarlo a un Web Worker: otro hilo, cero bloqueo del principal. Pero para la mayoría de casos, ceder el hilo cada N iteraciones ya convierte un congelamiento en algo imperceptible.

Lo que aprendí midiendo, no adivinando

INP me enseñó a dejar de confundir "rápido de cargar" con "rápido de usar". Son ejes distintos y el usuario siente sobre todo el segundo, porque pasa el 99% del tiempo interactuando, no cargando. La buena noticia es que casi siempre se arregla sin reescribir nada: encuentras la tarea larga con longtask, decides qué es urgente y qué puede esperar, y usas la herramienta que toca —useDeferredValue, useTransition, ceder el hilo o un worker—. Mi regla es simple: si una interacción bloquea el hilo más de 200ms, no es una micro-optimización opcional, es un bug de percepción de calidad. Y los bugs de percepción son los que hacen que la gente diga "no sé, se siente lento" y no vuelva.

Yohangel Ramos

Escrito por Yohangel Ramos

Senior Fullstack Developer y Tech Lead. Construyo con React, Next.js, Nest.js y AWS — y escribo sobre lo que aprendo en el camino.

Hablemos →

Sigue leyendo

IA

Startups hechas con IA en 2026: los números reales detrás del hype

AWS

El NAT Gateway que nadie pidió: la factura silenciosa de tu VPC