Cómo reduje el LCP de 5.8s a 2.5s en un e-commerce con Next.js
Por qué importa el LCP (más allá de Google)
- La imagen del hero
- La imagen del producto en las PDPs
- Las primeras tarjetas de producto visibles en las páginas de categoría
| LCP | Tasa de rebote | Conversión |
|---|---|---|
| 5.8s | 62% | 1.8% |
| 4.0s | 48% | 2.1% |
| 2.5s | 34% | 2.6% |
La auditoría: encontrando a los culpables
- Lighthouse (Chrome DevTools)
- WebPageTest (para pruebas en dispositivos reales)
- Vercel Analytics (datos de usuarios reales)
- Imágenes sin optimizar - Las imágenes del hero eran PNGs de más de 2MB
- JavaScript que bloqueaba el render - 800KB de JS antes del primer paint
- Sin priorización de imágenes - La imagen del LCP cargaba después de otros recursos
- Scripts de terceros - Analytics y widgets de chat cargando de forma síncrona
- Sin estrategia de cache - Cada visita descargaba todo de nuevo
Fix #1: Optimización de imágenes (la mayor ganancia)
Antes
// ❌ Raw img tag with massive PNG
<img src="/hero-banner.png" alt="Sale" />Después
// ✅ Next.js Image with priority
import Image from 'next/image'
<Image
src="/hero-banner.png"
alt="Sale"
width={1200}
height={600}
priority // Tells Next.js this is the LCP image
placeholder="blur"
blurDataURL={blurDataUrl}
/>- Conversión automática a WebP/AVIF (-70% en el tamaño de archivo)
- Srcset responsive (el tamaño correcto para cada dispositivo)
- Lazy loading para las imágenes debajo del fold
- Placeholder con blur para mejorar la performance percibida
Imágenes de producto a escala
// next.config.js
module.exports = {
images: {
formats: ['image/avif', 'image/webp'],
deviceSizes: [640, 750, 828, 1080, 1200],
imageSizes: [16, 32, 48, 64, 96, 128, 256],
},
}function ProductCard({ product }) {
return (
<div className="product-card">
<Image
src={product.image}
alt={product.name}
width={300}
height={300}
loading="lazy" // Not priority - these are below fold
placeholder="blur"
blurDataURL={product.blurHash}
/>
</div>
)
}Fix #2: Code splitting y optimización del bundle
Imports dinámicos para componentes pesados
// ❌ Before: Everything loads upfront
import { HeavyChart } from '@/components/HeavyChart'
import { ReviewsCarousel } from '@/components/ReviewsCarousel'
// ✅ After: Load when needed
import dynamic from 'next/dynamic'
const HeavyChart = dynamic(() => import('@/components/HeavyChart'), {
loading: () => <ChartSkeleton />,
ssr: false // Client-only component
})
const ReviewsCarousel = dynamic(() => import('@/components/ReviewsCarousel'), {
loading: () => <ReviewsSkeleton />
})Splitting por rutas
// pages/product/[slug].tsx
// Only loads product page code when visiting /product/*
// pages/checkout/index.tsx
// Checkout code separate from product browsingAnalizando el bundle
# Add to package.json scripts
"analyze": "ANALYZE=true next build"// next.config.js
const withBundleAnalyzer = require('@next/bundle-analyzer')({
enabled: process.env.ANALYZE === 'true',
})
module.exports = withBundleAnalyzer({
// config
})// ❌ Imports entire lodash (70KB)
import _ from 'lodash'
// ✅ Imports only what we need (4KB)
import debounce from 'lodash/debounce'Fix #3: Critical rendering path
Preconnect a los orígenes esenciales
// app/layout.tsx or pages/_document.tsx
<Head>
<link rel="preconnect" href="https://cdn.example.com" />
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="dns-prefetch" href="https://analytics.example.com" />
</Head>Optimización de fuentes
// ❌ Before: Fonts block rendering
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;700&display=swap" rel="stylesheet">
// ✅ After: Next.js font optimization
import { Inter } from 'next/font/google'
const inter = Inter({
subsets: ['latin'],
display: 'swap', // Show fallback while loading
preload: true,
})CSS crítico inline
// Critical CSS for hero section
<style dangerouslySetInnerHTML={{ __html: `
.hero { min-height: 60vh; }
.hero-image { object-fit: cover; }
`}} />Fix #4: Gestión de scripts de terceros
Diferir los scripts no esenciales
// ❌ Before: Blocks rendering
<script src="https://chat-widget.com/loader.js"></script>
// ✅ After: Load after page is interactive
<Script
src="https://chat-widget.com/loader.js"
strategy="lazyOnload" // Loads after everything else
/>Cargar analytics como debe ser
import Script from 'next/script'
// Google Analytics - load after hydration
<Script
src="https://www.googletagmanager.com/gtag/js?id=GA_ID"
strategy="afterInteractive"
/>Patrón facade para widgets pesados
function ChatWidget() {
const [loaded, setLoaded] = useState(false)
if (!loaded) {
return (
<button onClick={() => setLoaded(true)}>
💬 Chat with us
</button>
)
}
return <ActualChatWidget />
}Fix #5: Estrategia de caching
Generación estática donde sea posible
// Product pages: ISR with 60-second revalidation
export async function generateStaticParams() {
const products = await getTopProducts(100)
return products.map(p => ({ slug: p.slug }))
}
export const revalidate = 60 // Regenerate every 60 secondsHeaders de cache para assets
// next.config.js
module.exports = {
async headers() {
return [
{
source: '/images/:path*',
headers: [
{
key: 'Cache-Control',
value: 'public, max-age=31536000, immutable',
},
],
},
]
},
}Los resultados
| Métrica | Antes | Después | Cambio |
|---|---|---|---|
| LCP | 5.8s | 2.5s | -56% |
| FCP | 3.2s | 1.4s | -56% |
| TTI | 8.1s | 4.2s | -48% |
| Tamaño del bundle | 800KB | 320KB | -60% |
| Lighthouse | 54 | 92 | +70% |
| Métrica | Antes | Después | Cambio |
|---|---|---|---|
| Tasa de rebote | 62% | 34% | -45% |
| Conversión | 1.8% | 2.6% | +44% |
| Sesión promedio | 1:45 | 3:12 | +83% |
Aprendizajes clave
- Mide primero - No adivines. Usa datos de usuarios reales de Vercel Analytics o similar.
- Las imágenes suelen ser la mayor ganancia - El componente Image de Next.js es tu mejor aliado.
- El JavaScript es costoso - Cada KB de JS tiene un costo. Haz code splitting de forma agresiva.
- Los scripts de terceros se acumulan - Audita todo lo que carga en tu página.
- Performance = Ingresos - En e-commerce, la velocidad impacta directamente el resultado del negocio.
Herramientas que uso
- Lighthouse - Auditorías rápidas durante el desarrollo
- WebPageTest - Pruebas en dispositivos reales con vista de filmstrip
- Vercel Analytics - Métricas de usuarios reales en producción
- Bundle Analyzer - Para entender qué hay dentro de tu bundle de JS
¿Necesitas ayuda optimizando tu aplicación de Next.js? Hablemos →


