Cambiar tema

Next.js Core Web Vitals en la práctica: guía completa de optimización LCP/FCP/CLS

Easton editorial illustration: state-management shelf

El mes pasado, me hice cargo de la optimización del rendimiento de un proyecto de comercio electrónico: la puntuación de Lighthouse se estancó en torno a 65, el LCP (Largest Contentful Paint) rondaba los 3,5 segundos. La tasa de abandono era alta y la conversión no despegaba.

Dos semanas de optimización sistemática de Core Web Vitals después: Lighthouse a 95, LCP a 1,2 s. Y sobre todo, +28% de conversión. La optimización del rendimiento no se trata sólo de métricas: se trata de un valor empresarial concreto.

Según datos de 2025, sólo el 47% de los sitios cumplen con el estándar Core Web Vitals de Google. Un rendimiento deficiente puede costar entre un 8 % y un 35 % en ingresos, clasificaciones y conversiones. Esto no es alarmismo: es el precio real de los negocios.

En este artículo, comparto mi experiencia de campo sobre la optimización de Next.js. Allí encontrarás:

  • Métodos concretos para optimizar las 3 métricas clave (LCP, INP, CLS)
  • Más de 10 ejemplos de código listos para copiar
  • 5 errores de rendimiento más comunes
95
95
Puntuación Lighthouse optimizada
1,2 s
LCP optimizado
De 3,5 s
28%
Aumento de conversión
Valor de negocio de la optimización
47%
Sitios conformes
Datos Core Web Vitals 2025
Source: Datos de producción

Core Web Vitals: qué cambiará en 2025

Antes de optimizar, aclaremos las reglas del juego. Muchos artículos todavía hablan de FID (First Input Delay), aunque esta métrica se eliminó en marzo de 2024.

Las tres métricas clave actuales

Google ahora se centra en estos tres indicadores:

  1. LCP (Largest Contentful Paint) — pintura del contenido principal

    • Objetivo: ≤ 2,5 s
    • Mide el rendimiento de carga
    • Pesa el 25% en la puntuación Lighthouse Performance
  2. INP (Interacción con la siguiente pintura) — interacción hasta el siguiente renderizado

    • Objetivo: ≤ 200 ms
    • Mide la capacidad de respuesta a las interacciones.
    • Nueva métrica desde marzo de 2024, reemplaza FID
  3. CLS (Cambio de diseño acumulativo): cambio de diseño acumulativo

    • Objetivo: < 0,1
    • Mide la estabilidad visual

FCP (First Contentful Paint) ya no es una métrica de Core Web Vitals, pero sigue siendo importante para las primeras impresiones de los usuarios.

¿Por qué estar interesado?

Probablemente conozcas este escenario: abres un sitio, apuntas a un botón, la página salta y haces clic en otro lugar. Es un CLS malo.

O esperas varios segundos frente a una pantalla blanca preguntándote si se corta la conexión. Es un LCP demasiado lento.

Según el equipo de Chrome, el LCP promedio de los mejores sitios es de alrededor de 1220 ms. Si su LCP supera los 2,5 segundos, está detrás de la mayoría de sus competidores.

Optimización LCP: muestra rápidamente el elemento más grande

LCP ha sido la métrica en la que más he trabajado y en la que las ganancias son más visibles. He aquí cómo hacerlo.

Paso 1: Identifique su elemento LCP

Antes de optimizar, necesita saber qué elemento es el LCP. En general:

  • La imagen del héroe en la mitad superior de la página.
  • La miniatura de un vídeo.
  • Un bloque de título o texto grande.

Método rápido:

  1. Abra Chrome DevTools (F12)
  2. Ctrl+Mayús+P (Mac: Cmd+Mayús+P)
  3. Escriba “Mostrar renderizado”
  4. Marque “Ventajas web principales”
  5. Actualizar: el elemento LCP se muestra en la parte superior derecha

En el proyecto de comercio electrónico, el elemento LCP era la imagen principal de la página de inicio: un JPG de 2 MB. Ahí estaba el problema.

Optimización de imagen: use siguiente/imagen correctamente

Muchos piensan que usar el componente Imagen de Next.js es suficiente. Este no es el caso: cometí este error al principio y el LCP siguió siendo lento.

La prioridad es clave

Para la imagen LCP, debes decirle explícitamente al navegador: “¡Esta imagen es importante, cárgala como prioridad!”. » Next.js ofrece dos enfoques:

Next.js 13-15 (versiones actuales):

import Image from 'next/image';

export default function Hero() {
  return (
    <Image
      src="/hero-image.jpg"
      width={1200}
      height={630}
      priority  // ¡Clave! Prioridad de carga
      fetchPriority="high"  // Doble seguridad
      alt="Imagen principal del producto"
    />
  );
}

Next.js 16+ (última versión):

Next.js 16 desaprobó “prioridad” a favor de:

<Image
  src="/hero-image.jpg"
  width={1200}
  height={630}
  loading="eager"  // Carga inmediata, sin lazy load
  fetchPriority="high"  // Alta prioridad
  alt="Imagen principal del producto"
/>

¿Por qué dos atributos? prioridad agrega automáticamente una etiqueta de precarga; fetchPriority indica la importancia del recurso. Juntos funcionan mejor.

Para comparar con malas prácticas:

// ❌ Error: lazy load por defecto, LCP lento
<Image
  src="/hero-image.jpg"
  width={1200}
  height={630}
  alt="Imagen principal del producto"
/>

// ❌ Error: faltan dimensiones
<Image
  src="/hero-image.jpg"
  priority
  alt="Imagen principal del producto"
/>

La importancia de las dimensiones

El componente “Imagen” requiere “ancho” y “alto”; esto no es una restricción arbitraria, sino una protección contra CLS.

Para un diseño responsivo:

// Utiliser fill pour le responsive
<div style={{ position: 'relative', width: '100%', aspectRatio: '16/9' }}>
  <Image
    src="/hero-image.jpg"
    fill
    priority
    style={{ objectFit: 'cover' }}
    alt="Imagen principal del producto"
  />
</div>

El contenedor principal debe tener un tamaño o “relación de aspecto” explícito; de lo contrario, el navegador no sabe qué área asignar.

Optimización de fuentes: siguiente/fuente en línea automática

La carga lenta de fuentes también penaliza al LCP, especialmente las fuentes CJK que pesan varios MB. Next.js 13 introdujo next/font para automatizar la optimización.

Fuentes de Google:

// app/layout.jsx
import { Inter, Noto_Sans_SC } from 'next/font/google';

const inter = Inter({
  subsets: ['latin'],
  display: 'swap',  // Evita el parpadeo de texto
});

const notoSansSC = Noto_Sans_SC({
  subsets: ['chinese-simplified'],
  weight: ['400', '700'],
  display: 'swap',
});

export default function RootLayout({ children }) {
  return (
    <html lang="zh-CN" className={`${inter.className} ${notoSansSC.className}`}>
      <body>{children}</body>
    </html>
  );
}

Fuentes personalizadas:

import localFont from 'next/font/local';

const myFont = localFont({
  src: './my-font.woff2',
  display: 'swap',
});

export default function Layout({ children }) {
  return <div className={myFont.className}>{children}</div>;
}

next/font optimiza automáticamente:

  • CSS de fuente en línea, menos solicitudes de red
  • Subconjunto de personajes, solo carga lo necesario
  • Precargar archivos de fuentes
  • Se eliminaron las compensaciones de diseño.

Después de migrar a next/font, el LCP cayó otros 0,3 s.

Reducir el tiempo de respuesta del servidor

¿Imágenes y fuentes optimizadas, pero el LCP sigue siendo lento? TTFB (tiempo hasta el primer byte) puede ser la causa.

Favorecer la generación estática

Next.js ofrece varios modos de renderizado, del más rápido al más lento:

  1. SSG (Generación de sitios estáticos): HTML generado durante la construcción, el más rápido
  2. ISR (Regeneración estática incremental): regeneración estática según demanda
  3. SSR (Representación del lado del servidor): HTML generado en cada solicitud, más lento
  4. CSR (Representación del lado del cliente): renderización del lado del cliente, LCP más lento

Utilice SSG siempre que sea posible:

// app/blog/[slug]/page.jsx
export async function generateStaticParams() {
  const posts = await getPosts();
  return posts.map((post) => ({
    slug: post.slug,
  }));
}

export default async function BlogPost({ params }) {
  const post = await getPost(params.slug);
  return <article>{/* ... */}</article>;
}

Si los datos deben permanecer actualizados, utilice ISR:

// app/products/[id]/page.jsx
export const revalidate = 3600; // Régénération toutes les heures

export default async function ProductPage({ params }) {
  const product = await getProduct(params.id);
  return <div>{/* ... */}</div>;
}

CDN y red perimetral

En Vercel, los recursos estáticos se distribuyen a través de la red perimetral global, lo que reduce en gran medida el TTFB.

En otras plataformas, combínelo con Cloudflare, AWS CloudFront o CDN equivalente.

Evite cálculos pesados del lado del servidor

También caí en esta trampa: el procesamiento de datos complejos en el lado del servidor tomó 500 ms para cada renderizado, agotando el LCP.

Contraejemplo:

// ❌ Error: cálculo en cada solicitud
export default async function Page() {
  const data = await fetchData();
  const processed = heavyProcessing(data); // 500 ms
  return <div>{processed}</div>;
}

Buen enfoque:

Almacene en caché el resultado o calcule en la compilación:

// ✅ Correcto: cálculo en build
export async function generateStaticParams() {
  const data = await fetchData();
  const processed = heavyProcessing(data);
  await saveProcessedData(processed);
}

export default async function Page() {
  const processed = await getProcessedData(); // Lecture directe
  return <div>{processed}</div>;
}

Optimización de CLS: elimine las interrupciones del diseño

CLS (Cambio de diseño acumulativo) es la métrica más frustrante. La página salta de repente: mala experiencia. Este problema me mantuvo ocupado por un tiempo.

Reservar dimensiones de imagen y medios

La causa más común: una imagen que se carga y cambia el diseño. Solución: indicar las dimensiones con antelación.

El componente Image Next.js se encarga de esto:

// ✅ Correcto: dimensiones explícitas, sin CLS
<Image
  src="/product.jpg"
  width={400}
  height={300}
  alt="Imagen del producto"
/>

¿Y para imágenes dinámicas?

Desde un CMS, sin saber las dimensiones:

// ✅ Correcto: aspect-ratio para reservar espacio
<div style={{ position: 'relative', width: '100%', aspectRatio: '4/3' }}>
  <Image
    src={dynamicImageUrl}
    fill
    style={{ objectFit: 'cover' }}
    alt="Imagen dinámica"
  />
</div>

La misma lógica para el vídeo:

<video
  width="1280"
  height="720"
  poster="/video-poster.jpg"
  controls
>
  <source src="/video.mp4" type="video/mp4" />
</video>

Marcador de posición para contenido dinámico

Los anuncios, los banners de notificación, las incrustaciones (tarjetas de Twitter, etc.) provocan CLS. Reserva el espacio con antelación.

Contenedor publicitario:

// ✅ Correcto: altura reservada
<div
  style={{
    minHeight: '250px',  // Hauteur standard Google AdSense
    backgroundColor: '#f0f0f0'  // Couleur de placeholder
  }}
>
  {/* Script de anuncios */}
  <ins className="adsbygoogle" />
</div>

Banner de notificación:

No dejes que de repente aparezca una venda en los ojos:

// ❌ Error: aparece de repente y empuja la página
{showBanner && <NotificationBanner />}

// ✅ Correcto: espacio reservado
<div style={{ minHeight: '60px' }}>
  {showBanner ? <NotificationBanner /> : <div style={{ height: '60px' }} />}
</div>

Pantallas de esqueleto:

Para contenido cargado dinámicamente, utilice esqueletos:

function ProductList() {
  const { data, isLoading } = useQuery('products', fetchProducts);

  if (isLoading) {
    return (
      <div className="grid grid-cols-3 gap-4">
        {Array.from({ length: 6 }).map((_, i) => (
          <div key={i} className="skeleton" style={{ height: '300px' }} />
        ))}
      </div>
    );
  }

  return (
    <div className="grid grid-cols-3 gap-4">
      {data.map(product => <ProductCard key={product.id} {...product} />)}
    </div>
  );
}

Optimización de carga de fuentes

La carga de fuentes también provoca CLS: cambio de fuente visible o parpadeante.

next/font maneja esto automáticamente, pero puedes ajustarlo:

const inter = Inter({
  subsets: ['latin'],
  display: 'optional',  // Si la fuente no carga a tiempo, usar la del sistema
  adjustFontFallback: true,  // Ajusta el fallback para limitar el salto
});

Opciones para mostrar:

  • swap: primero retrocede, luego cambia (puede causar CLS)
  • opcional: si la fuente no está lista a tiempo, conserve la fuente alternativa (recomendado)
  • block: texto oculto brevemente mientras se espera la fuente (no recomendado)
  • fallback: compromiso entre swap y opcional

Recomiendo “opcional”: es posible que la fuente personalizada no se muestre, pero la experiencia permanece estable.

Evite el uso de JavaScript responsivo

¡El error más común de CLS en 2025! Muchos proyectos de Next.js cometen este error.

Código normalmente problemático:

// ❌ Error: CLS grave
import { useMediaQuery } from '@/hooks/useMediaQuery';

export default function ResponsiveLayout() {
  const isMobile = useMediaQuery('(max-width: 768px)');

  return (
    <div>
      {isMobile ? (
        <MobileNav />
      ) : (
        <DesktopNav />
      )}
    </div>
  );
}

¿Por qué CLS?

  1. En la primera representación, JavaScript aún no se ha ejecutado: isMobile es indefinido o es un valor predeterminado
  2. Después de la ejecución, useMediaQuery devuelve el valor verdadero
  3. Volver a renderizar, diseño que cambia abruptamente

En SSR, es peor: el servidor no conoce el ancho de la pantalla del cliente.

Buen enfoque: consultas de medios CSS

// ✅ Correcto: mostrar/ocultar con CSS, sin CLS
export default function ResponsiveLayout() {
  return (
    <>
      <nav className="mobile-nav md:hidden">
        <MobileNav />
      </nav>
      <nav className="desktop-nav hidden md:block">
        <DesktopNav />
      </nav>
    </>
  );
}

O con CSS-en-JS:

export default function ResponsiveLayout() {
  return (
    <div className="responsive-container">
      <MobileNav />
      <DesktopNav />
      <style jsx>{`
        .responsive-container > :global(.mobile-nav) {
          display: block;
        }
        .responsive-container > :global(.desktop-nav) {
          display: none;
        }
        @media (min-width: 768px) {
          .responsive-container > :global(.mobile-nav) {
            display: none;
          }
          .responsive-container > :global(.desktop-nav) {
            display: block;
          }
        }
      `}</style>
    </div>
  );
}

Si realmente necesita distinguir en el lado JS (diferentes solicitudes según el dispositivo), obtenga el User-Agent en el lado del servidor:

// app/page.jsx
import { headers } from 'next/headers';

export default async function Page() {
  const headersList = headers();
  const userAgent = headersList.get('user-agent') || '';
  const isMobile = /mobile/i.test(userAgent);

  return (
    <div>
      {isMobile ? <MobileView /> : <DesktopView />}
    </div>
  );
}

El servidor y el cliente representan lo mismo: sin CLS.

Optimización FCP: acelera el primer contenido visible

FCP no es una métrica de Core Web Vitals, pero da forma a la primera impresión. Una pantalla en blanco demasiado larga y el usuario cierra la pestaña.

Optimizar los recursos críticos

El FCP lento a menudo proviene de recursos que bloquean el procesamiento. Abra el panel Cobertura de Chrome DevTools para detectar CSS y JavaScript no utilizados.

CSS crítico en línea:

Next.js optimiza CSS automáticamente, pero puedes incorporar estilos esenciales:

// app/layout.jsx
export default function RootLayout({ children }) {
  return (
    <html>
      <head>
        <style
          dangerouslySetInnerHTML={{
            __html: `
              /* CSS crítico: estilos indispensables del primer pantallazo */
              body { margin: 0; font-family: system-ui; }
              .hero { height: 100vh; }
            `,
          }}
        />
      </head>
      <body>{children}</body>
    </html>
  );
}

Aplazar CSS no crítico:

Para estilos que no son de primera pantalla (secciones modales y plegables):

// Import dynamique du CSS dans le composant
import dynamic from 'next/dynamic';

const Modal = dynamic(() => import('./Modal'), {
  loading: () => <p>Loading...</p>,
});

Gestión de scripts de terceros

Google Analytics, anuncios, complementos sociales: tantos factores que afectan el rendimiento. El FCP de muchos sitios sufre esto.

Usar siguiente/script:

Next.js proporciona el componente Script para controlar el tiempo de carga:

import Script from 'next/script';

export default function Layout({ children }) {
  return (
    <>
      {children}

      {/* Google Analytics : après interactivité */}
      <Script
        src="https://www.googletagmanager.com/gtag/js?id=GA_MEASUREMENT_ID"
        strategy="lazyOnload"
      />
      <Script id="ga-init" strategy="lazyOnload">
        {`
          window.dataLayer = window.dataLayer || [];
          function gtag(){dataLayer.push(arguments);}
          gtag('js', new Date());
          gtag('config', 'GA_MEASUREMENT_ID');
        `}
      </Script>

      {/* Facebook Pixel : chargement différé */}
      <Script
        src="https://connect.facebook.net/en_US/fbevents.js"
        strategy="lazyOnload"
      />
    </>
  );
}

valores de estrategia:

  • beforeInteractive: antes de la interactividad (scripts críticos)
  • afterInteractive: después de la interactividad (predeterminado)
  • lazyOnload: en inactivo (recomendado para análisis y anuncios)
  • worker: en un Web Worker (experimental)

Al cambiar todos los scripts no esenciales a “lazyOnload”, el FCP ganó 0,8 s.

División de código y carga diferida

Next.js divide el código automáticamente, pero el refinamiento manual sigue siendo útil.

Componentes de carga diferida:

Para lo que no se ve a la primera pintura:

import dynamic from 'next/dynamic';

// Lazy load du composant commentaires
const Comments = dynamic(() => import('./Comments'), {
  loading: () => <div>Cargando comentarios...</div>,
  ssr: false,  // Sin renderizado en servidor
});

export default function BlogPost({ post }) {
  return (
    <article>
      <h1>{post.title}</h1>
      <div>{post.content}</div>
      {/* Chargé quand l'utilisateur scroll jusqu'ici */}
      <Comments postId={post.id} />
    </article>
  );
}

Carga diferida de bibliotecas de terceros:

Algunas bibliotecas son pesadas (gráficos, editores enriquecidos): cárguelas a pedido.

Caso: Chart.js: 800 KB guardados

Un proyecto importó Chart.js en cada página mientras que solo el panel lo usaba. 800 KB de JS inútiles por todas partes.

Antes de la optimización:

// ❌ Error: import global, todas las páginas lo cargan
import { Chart } from 'chart.js';

export default function Dashboard() {
  return <canvas ref={chartRef} />;
}

Después de la optimización:

// ✅ Correcto: carga bajo demanda
import dynamic from 'next/dynamic';

const ChartComponent = dynamic(() => import('./ChartComponent'), {
  loading: () => <div>Cargando gráfico...</div>,
  ssr: false,
});

export default function Dashboard() {
  return <ChartComponent />;
}

// ChartComponent.jsx
import { Chart } from 'chart.js';

export default function ChartComponent() {
  return <canvas ref={chartRef} />;
}

Solo los visitantes del panel descargan Chart.js.

Carga diferida de imágenes:

Excluyendo la imagen LCP, las otras imágenes deben cargarse de forma diferida:

<Image
  src="/feature-image.jpg"
  width={600}
  height={400}
  loading="lazy"  // Valor por defecto, se puede omitir
  alt="Presentación de funciones"
/>

##Monitoreo y optimización continuos

La optimización no es algo que se haga de una sola vez. El rendimiento se mantiene en el tiempo.

Lighthouse CI automáticamente

Lanzar Lighthouse a mano lleva tiempo y lo olvidas. Es mejor integrarlo con CI/CD.

Instalación:

npm install -D @lhci/cli

Configuración lighthouserc.js:

module.exports = {
  ci: {
    collect: {
      url: ['http://localhost:3000/', 'http://localhost:3000/products'],
      numberOfRuns: 3,  // 3 ejecuciones, promedio
    },
    assert: {
      assertions: {
        'categories:performance': ['error', { minScore: 0.9 }],  // Performance ≥ 90
        'first-contentful-paint': ['error', { maxNumericValue: 2000 }],  // FCP ≤ 2s
        'largest-contentful-paint': ['error', { maxNumericValue: 2500 }],  // LCP ≤ 2,5s
        'cumulative-layout-shift': ['error', { maxNumericValue: 0.1 }],  // CLS < 0,1
      },
    },
    upload: {
      target: 'temporary-public-storage',
    },
  },
};

Acciones de GitHub:

# .github/workflows/lighthouse.yml
name: Lighthouse CI
on: [push]
jobs:
  lighthouse:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - uses: actions/setup-node@v3
      - run: npm install
      - run: npm run build
      - run: npm run start &
      - run: npx @lhci/cli autorun

Cada pulsación activa Lighthouse. Una regresión del rendimiento hace que el CI falle.

Monitoreo de usuarios reales (RUM)

Medidas faro en laboratorio: la experiencia real puede diferir. Recopile Core Web Vitals del campo.

Vercel Analytics:

En Vercel, activación con un clic:

// app/layout.jsx
import { Analytics } from '@vercel/analytics/react';

export default function RootLayout({ children }) {
  return (
    <html>
      <body>
        {children}
        <Analytics />
      </body>
    </html>
  );
}

biblioteca web-vitals:

Sin Vercel, utilice la biblioteca web-vitals de Google:

npm install web-vitals
// app/layout.jsx
'use client';

import { useEffect } from 'react';
import { onLCP, onFID, onCLS, onINP } from 'web-vitals';

function sendToAnalytics(metric) {
  fetch('/api/analytics', {
    method: 'POST',
    body: JSON.stringify(metric),
  });
}

export default function Analytics() {
  useEffect(() => {
    onLCP(sendToAnalytics);
    onINP(sendToAnalytics);
    onCLS(sendToAnalytics);
  }, []);

  return null;
}

Consola de búsqueda de Google:

El informe “Señales web esenciales” muestra el rendimiento del campo y marca las páginas para mejorar.

Errores y soluciones comunes

Finalmente, los 5 errores más comunes:

Error 1: Demasiado JavaScript del lado del cliente

Muchos desarrolladores hacen todo en React: la página depende completamente de JS. Si la carga falla o se retrasa, pantalla blanca.

Soluciones:

  • Favorecer los componentes del servidor (Next.js 13+ predeterminado)
  • 'usar cliente' sólo si es necesario
  • Mejora progresiva: funcionalidad básica primero, enriquecimiento después

Trampa 2: ignorar el rendimiento móvil

Rápido en computadoras de escritorio no significa rápido en dispositivos móviles. CPU débil, red lenta: los problemas van en aumento.

Soluciones:

  • Prueba en modo “Móvil” en Lighthouse
  • Limitación de red (Chrome DevTools → Red)
  • Aceleración de la CPU (Chrome DevTools → Rendimiento → CPU)

Error 3: scripts de terceros no optimizados

Google Analytics, Facebook Pixel, Intercom, Hotjar… cada script hace que la página sea más pesada.

Soluciones:

  • Ajustar con next/script, strategy="lazyOnload"
  • Auditoría periódica: ¿qué guiones son realmente necesarios?
  • Seguimiento del servidor en lugar del cliente cuando sea posible

Trampa 4: elección incorrecta del formato de imagen

¿Aún en PNG y JPG? WebP y AVIF ahorran entre un 30 y un 50 % de volumen.

Soluciones:

El componente Image Next.js se convierte automáticamente; asegúrese de que el servidor lo admita:

// next.config.js
module.exports = {
  images: {
    formats: ['image/avif', 'image/webp'],  // Formatos modernos prioritarios
  },
};

Trampa 5: descuidar el rendimiento del servidor

Una interfaz perfecta no compensa un servidor lento.

Soluciones:

  • Optimizar consultas BDD (indexar, evitar N+1)
  • Caché (Redis, CDN)
  • Monitoreo de servidores (APM: New Relic, Datadog)

Resumen

Recapitulemos:

Puntos clave del LCP:

  • priority y fetchPriority="high" en la imagen LCP
  • siguiente/fuente para fuentes
  • SSG e ISR como prioridad, menos cálculo del servidor
  • CDN para reducir TTFB

Puntos clave de CLS:

  • Dimensiones explícitas para imágenes y medios.
  • Espacio reservado para contenido dinámico (anuncios, banners)
  • No hay diseño responsivo a través de enlaces de JavaScript
  • next/font para limitar los saltos de fuente

Puntos clave del FCP:

  • Aplazar recursos no críticos
  • Scripts de terceros a través de next/script con lazyOnload
  • Carga diferida de grandes bibliotecas y componentes excluyendo la primera pantalla

Optimización continua:

  • CI de faro automatizado
  • Datos reales del usuario (RUM)
  • Auditoría y limpieza periódicas.

La optimización del rendimiento sigue un ciclo de “medir → optimizar → volver a medir”. No hay una solución única: atención y mejora continuas.

Cuando la puntuación de Lighthouse finalmente superó los 90, la satisfacción fue real. Y el aumento de las conversiones demostró que el esfuerzo valió la pena.

Plan de acción

Depende de ti. Recomiendo este orden:

  1. Inmediato:

    • Pruebe su proyecto Next.js con Lighthouse
    • Identificar el elemento LCP: ¿se ha establecido una “prioridad”?
    • Busque useMediaQuery o código similar que cause CLS
  2. Esta semana:

    • Optimizar imagen LCP (prioridad + fetchPriority)
    • Migrar fuentes a siguiente/fuente
    • Agregar marcadores de posición para contenido dinámico
  3. La próxima semana:

    • Scripts de terceros en lazyOnload
    • Carga diferida de grandes bibliotecas y componentes excluyendo la primera pantalla
    • Configurar Lighthouse CI
  4. Continuo:

    • Verifique la puntuación de Lighthouse todos los meses
    • Realice un seguimiento del informe Core Web Vitals en Search Console
    • Regresiones rápidamente correctas.

La optimización del rendimiento no es un proyecto único: es un proceso continuo. ¡Feliz optimización!

Flujo completo de optimización de Core Web Vitals en Next.js

Optimizar LCP, INP y CLS para subir la puntuación de Lighthouse por encima de 90

⏱️ Estimated time: 8 hr

  1. 1

    Step 1: Optimizar LCP (Largest Contentful Paint)

    Objetivo: ≤2,5 s

    Métodos:
    • Usar next/image para optimizar imágenes (conversión automática a WebP/AVIF)
    • Añadir priority a imágenes críticas del primer pantallazo
    • Precargar recursos clave (con <link rel="preload">)
    • Optimizar tiempo de respuesta del servidor (CDN, Edge Functions)
    • Reducir recursos que bloquean el renderizado (carga diferida de CSS/JS no crítico)
    • Usar React Server Components para reducir JS en el cliente

    Herramientas:
    • Informe Performance de Lighthouse
    • Panel Performance de Chrome DevTools
    • Pruebas online con WebPageTest
  2. 2

    Step 2: Optimizar INP (Interaction to Next Paint)

    Objetivo: ≤200 ms

    Métodos:
    • Reducir tiempo de ejecución de JavaScript (code splitting, lazy loading)
    • Usar React Server Components para reducir JS en el cliente
    • Optimizar manejo de eventos (debounce, throttle, delegación)
    • Evitar tareas largas que bloqueen el hilo principal (Web Workers)
    • Optimizar scripts de terceros (carga diferida, async/defer)
    • Usar Suspense y renderizado en streaming

    Herramientas:
    • Panel Performance de Chrome DevTools
    • Extensión Web Vitals para Chrome
    • Herramientas de Real User Monitoring (RUM)
  3. 3

    Step 3: Optimizar CLS (Cumulative Layout Shift)

    Objetivo: ≤0,1

    Métodos:
    • Definir ancho y alto de imágenes y vídeos (o usar aspect-ratio)
    • Evitar insertar contenido dinámico (reservar espacio)
    • Usar font-display: swap para evitar desplazamientos por fuentes
    • Reservar espacio para anuncios (evitar que empujen el contenido al cargar)
    • Usar CSS Grid/Flexbox en lugar de posicionamiento absoluto
    • Evitar insertar contenido nuevo encima del existente

    Herramientas:
    • Informe CLS de Lighthouse
    • Registro Layout Shift en el panel Performance de Chrome DevTools
    • Visualización CLS en WebPageTest
  4. 4

    Step 4: Usar las funciones de optimización de Next.js

    Trucos de Next.js:
    • Componente Image para optimizar imágenes automáticamente
    • Font Optimization para optimizar fuentes
    • React Server Components para reducir JS en el cliente
    • Streaming SSR para acelerar el primer pantallazo
    • Dynamic Imports para code splitting
    • next/dynamic para cargar componentes de forma diferida

    Comprobaciones:
    • Configuración de rendimiento en next.config.js
    • Revisar tamaño del bundle (npm run build)
  5. 5

    Step 5: Monitorizar y optimizar de forma continua

    Herramientas de monitorización:
    • Informe Core Web Vitals de Google Search Console
    • Vercel Analytics (si usas Vercel)
    • Extensión Web Vitals para Chrome
    • Herramientas de Real User Monitoring (RUM)

    Optimización continua:
    • Revisar la puntuación de Lighthouse cada mes
    • Seguir el informe Core Web Vitals de Search Console
    • Corregir regresiones de rendimiento a tiempo
    • Probar el efecto de las optimizaciones con A/B testing

FAQ

¿Cuáles son las tres métricas de Core Web Vitals?
LCP (Largest Contentful Paint) ≤2,5 s, mide el rendimiento de carga; INP (Interaction to Next Paint) ≤200 ms, mide la velocidad de respuesta a interacciones (sustituyó a FID en 2024); CLS (Cumulative Layout Shift) ≤0,1, mide la estabilidad del diseño. Estas tres métricas afectan directamente al posicionamiento en Google.
¿Cómo optimizar LCP?
Métodos: 1) Usar next/image para optimizar imágenes; 2) Añadir priority a imágenes críticas del primer pantallazo; 3) Precargar recursos clave; 4) Optimizar tiempo de respuesta del servidor; 5) Reducir recursos que bloquean el renderizado; 6) Usar React Server Components. Objetivo: LCP ≤2,5 s.
¿Cuál es la diferencia entre INP y FID?
INP es la nueva métrica que sustituyó a FID en marzo de 2024. FID solo mide la primera interacción; INP mide el tiempo de respuesta de todas las interacciones. INP es más completa y refleja mejor la experiencia real del usuario. El valor objetivo es ≤200 ms.
¿Cómo evitar CLS (desplazamiento de diseño)?
Métodos: 1) Definir ancho y alto de imágenes y vídeos; 2) Evitar insertar contenido dinámico; 3) Usar font-display: swap; 4) Reservar espacio para anuncios; 5) Usar CSS Grid/Flexbox; 6) Evitar insertar contenido nuevo encima del existente. Objetivo: CLS ≤0,1.
¿Cuánto tarda en verse el efecto tras optimizar el rendimiento?
Las métricas técnicas (puntuación de Lighthouse) se ven de inmediato. Pero las métricas de negocio (conversión, posicionamiento) suelen tardar 1-3 meses. Google necesita tiempo para reevaluar el sitio y los datos de comportamiento de usuarios deben acumularse. Se recomienda monitorizar y optimizar de forma continua.
¿Qué funciones de Next.js ayudan a optimizar el rendimiento?
React Server Components reduce JS en el cliente, el componente Image optimiza imágenes automáticamente, Font Optimization optimiza fuentes, Streaming SSR acelera el primer pantallazo, Dynamic Imports permite code splitting, y hay code splitting y tree-shaking automáticos. Aprovechar estas funciones mejora el rendimiento de forma notable.
¿Cómo monitorizar Core Web Vitals?
Herramientas: 1) Informe Core Web Vitals de Google Search Console (datos de usuarios reales); 2) Lighthouse (datos de laboratorio); 3) Extensión Web Vitals para Chrome; 4) Vercel Analytics (si usas Vercel); 5) Herramientas de Real User Monitoring. Se recomienda combinar datos de laboratorio y de usuarios reales.

16 min de lectura · Publicado el: 19 dic 2025 · Actualizado el: 21 ago 2026

Comentarios

Inicia sesión con GitHub para dejar un comentario

Easton BlogEaston Blog