Optimización Avanzada de Core Web Vitals en Next.js 15: INP, LCP y CLS al 100%

La velocidad de carga y la reactividad visual ya no son solo métricas técnicas: impactan directamente en el posicionamiento orgánico en Google (SEO), el coste de adquisición de clientes y las tasas de conversión en e-commerce. Con la introducción de Interaction to Next Paint (INP) como métrica oficial de Core Web Vitals reemplazando a FID, optimizar las aplicaciones web desarrolladas en Next.js 15 requiere técnicas avanzadas de renderizado en servidor, streaming de componentes y minimización del hilo principal (main thread).

Las 3 Métricas Críticas de Core Web Vitals#
+-------------------------------------------------------------------------------+
| MÉTRICAS CORE WEB VITALS OFICIALES |
+-------------------------------------------------------------------------------+
| |
| +------------------------+ +------------------------+ +-----------------+ |
| | 1. LCP | | 2. INP | | 3. CLS | |
| | Largest Contentful | | Interaction to Next | | Cumulative | |
| | Paint (Velocidad) | | Paint (Reactividad) | | Layout Shift | |
| +------------------------+ +------------------------+ +-----------------+ |
| | • Objetivo: < 2.5s | | • Objetivo: < 200ms | | • Objetivo: <0.1| |
| | • Impacto: Portadas, | | • Impacto: Clics, taps,| | • Impacto: Salto| |
| | fuentes y banners | | menús y modales | | de elementos | |
| +------------------------+ +------------------------+ +-----------------+ |
| |
+-------------------------------------------------------------------------------+
1. Optimizando LCP (Largest Contentful Paint)#
El LCP mide el tiempo que tarda en visualizarse el elemento visual más grande de la pantalla (habitualmente la imagen de cabecera o el titular principal).
Reglas de oro para optimizar imágenes en Next.js:#
- Atributo
priorityexclusivo en el elemento principal: Evita que la imagen sea diferida (lazy loaded), forzando una precarga inmediata en el HTML. - Atributo
sizesexplícito: Evita que el navegador descargue imágenes de 2000px en pantallas móviles de 390px. - Formatos modernos (AVIF / WebP): Configurar Next.js para negociar compresión AVIF automática.
// next.config.ts
import type { NextConfig } from 'next';
const nextConfig: NextConfig = {
images: {
formats: ['image/avif', 'image/webp'],
minimumCacheTTL: 31536000, // 1 año de caché para assets inmutables
deviceSizes: [640, 750, 828, 1080, 1200, 1920],
imageSizes: [16, 32, 48, 64, 96, 128, 256, 384],
},
};
export default nextConfig;
Componente de Portada Optimizado#
import Image from 'next/image';
export function HeroCover({ src, alt }: { src: string; alt: string }) {
return (
<div className="relative w-full aspect-[16/9] overflow-hidden rounded-2xl bg-slate-900">
<Image
src={src}
alt={alt}
fill
priority
sizes="(max-width: 768px) 100vw, (max-width: 1200px) 80vw, 1200px"
className="object-cover"
/>
</div>
);
}
2. Dominando INP (Interaction to Next Paint)#
El INP mide la latencia de todas las interacciones de un usuario a lo largo de su visita. Ocurre cuando tareas pesadas de JavaScript bloquean el hilo principal e impiden que el navegador actualice la pantalla tras un clic.
Estrategia 1: useTransition para operaciones no bloqueantes#
Al filtrar listados grandes o realizar búsquedas en vivo, envolver la actualización del estado en startTransition permite que el navegador atienda clics del usuario sin congelarse:
'use client';
import { useState, useTransition } from 'react';
export function LiveFilter({ items }: { items: string[] }) {
const [query, setQuery] = useState('');
const [filteredList, setFilteredList] = useState(items);
const [isPending, startTransition] = useTransition();
const handleSearch = (e: React.ChangeEvent<HTMLInputElement>) => {
const value = e.target.value;
setQuery(value); // Actualización urgente e inmediata del input
startTransition(() => {
// Actualización diferida de baja prioridad (no bloquea el hilo principal)
const results = items.filter((item) =>
item.toLowerCase().includes(value.toLowerCase())
);
setFilteredList(results);
});
};
return (
<div>
<input
type="text"
value={query}
onChange={handleSearch}
placeholder="Buscar..."
className="px-4 py-2 bg-slate-800 rounded-lg text-white"
/>
{isPending && <span className="text-xs text-slate-400 ml-2">Filtrando...</span>}
<ul className="mt-4">
{filteredList.map((item) => (
<li key={item} className="py-1 text-slate-300">{item}</li>
))}
</ul>
</div>
);
}
Estrategia 2: Carga Dinámica (Lazy Import) de Componentes Pesados#
Modales complejos, gráficos interactivos o editores de texto no deben cargarse en el bundle inicial:
import dynamic from 'next/dynamic';
const DynamicChartModal = dynamic(
() => import('@/components/HeavyAnalyticsChart'),
{
loading: () => <div className="h-64 bg-slate-800 animate-pulse rounded-xl" />,
ssr: false, // Solo se inicializa en el cliente cuando el usuario abre el modal
}
);
3. Eliminando el CLS (Cumulative Layout Shift)#
El CLS evalúa la estabilidad visual. Ocurre cuando elementos sin dimensiones fijas (banners publicitarios, imágenes sin ratio o fuentes web que parpadean) empujan el contenido mientras el usuario lee.
Soluciones Arquitectónicas:#
- Fuentes con
next/font: Descarga las fuentes en tiempo de compilación y calcula el tamaño de reemplazo (fallback adjust) para erradicar el parpadeo tipográfico (FOIT/FOUT). - Contenedores con Aspect Ratio Fijo: Utilizar clases como
aspect-[16/9]omin-h-[300px]en contenedores dinámicos para reservar el espacio antes de que el contenido cargue. - Skeletons en Streaming con React Suspense:
import { Suspense } from 'react';
import { PostComments } from './PostComments';
import { CommentsSkeleton } from './CommentsSkeleton';
export default function BlogPostPage() {
return (
<article className="max-w-4xl mx-auto py-10">
<h1>Título del Artículo</h1>
<p>Contenido del post...</p>
{/* El skeleton reserva el espacio exacto, eliminando CLS */}
<Suspense fallback={<CommentsSkeleton />}>
<PostComments />
</Suspense>
</article>
);
}
Resultados y Puntuaciones en Producción#
| Indicador | Antes de Optimizar | Con Next.js 15 Optimizado | Estado Google |
|---|---|---|---|
| LCP (Largest Contentful Paint) | 3.8s (Malo) | 0.7s | 🟢 Bueno |
| INP (Interaction to Next Paint) | 320ms (Malo) | 38ms | 🟢 Bueno |
| CLS (Cumulative Layout Shift) | 0.28 (Malo) | 0.002 | 🟢 Bueno |
| Puntuación Google Lighthouse | 64 / 100 | 100 / 100 | 🏆 Excelente |
Conclusión#
Alcanzar la puntuación máxima en Core Web Vitals en Next.js 15 requiere una combinación metódica de Server Components, Streaming con Suspense, optimización rigurosa de imágenes/fuentes y el uso de useTransition para liberar el hilo principal.
¿Necesitas realizar una auditoría de rendimiento web y optimizar la velocidad y SEO técnico de tu sitio o tienda online? Consulta nuestros Servicios de Desarrollo Web o solicita una auditoría con Brayan Developer.
¿Te gustaría profundizar en estos temas?
Aprende sobre desarrollo de software, apps a medida, automatizaciones con N8N, Next.js y Cloud con casos reales.


