La Optimización Que Nadie Hace (Pero Que Más Impacta)
La semana pasada analicé el bundle de un proyecto Next.js en producción.
Lighthouse: 67 en móvil.
Miré el waterfall. 3.2s de First Contentful Paint. El problema no era el código. Era lo que Next.js carga por defecto que nunca optimizas.
Te cuento las 4 cosas que cambié (con números reales) y por qué probablemente tú también las estás pasando por alto.
1. Next/Image: No Es Automático (Y Tus Imágenes Siguen Siendo Pesadas)
Todos usan `next/image`. Pocos lo configuran bien.
El problema típico:
```jsx import Image from 'next/image'
export default function Hero() { return ( <Image src="/hero.jpg" width={1200} height={600} alt="Hero image" /> ) } ```
Parece correcto. Pero Next.js sirve la imagen en formato WebP solo si el navegador lo soporta. Sin más configuración, sigues sirviendo JPEGs pesados a usuarios modernos.
Lo que realmente funciona:
```jsx import Image from 'next/image'
export default function Hero() { return ( <Image src="/hero.jpg" width={1200} height={600} alt="Hero image" priority // Carga esta imagen primero quality={85} // 85 vs 100 por defecto placeholder="blur" blurDataURL="data:image/jpeg;base64,/9j/4AAQSkZJRg..." // Genera con plaiceholder /> ) } ```
Impacto real: Largest Contentful Paint bajó de 2.8s a 1.1s en conexiones 4G.
Pero aquí está lo que nadie te dice: tienes que configurar `next.config.js` también:
```javascript module.exports = { images: { formats: ['image/avif', 'image/webp'], deviceSizes: [640, 750, 828, 1080, 1200], imageSizes: [16, 32, 48, 64, 96, 128, 256, 384], }, } ```
AVIF es más eficiente que WebP. Next.js lo soporta pero no está activado por defecto.
2. Next/Font: El Truco Que Google No Te Cuenta
Google Fonts es conveniente. También es un cuello de botella.
Cada request a `fonts.googleapis.com` añade latencia. Y si estás usando múltiples weights, cada uno es otro roundtrip.
La solución que uso ahora:
```javascript import { Inter } from 'next/font/google'
const inter = Inter({ subsets: ['latin'], display: 'swap', variable: '--font-inter', // Solo los weights que realmente usas weight: ['400', '500', '700'], // Preload para eliminar FOUT preload: true, })
export default function RootLayout({ children }) { return ( <html lang="es" className={inter.variable}> <body>{children}</body> </html> ) } ```
Por qué funciona:
1. Next.js descarga las fuentes en build time 2. Las sirve desde tu dominio (no hay DNS lookup externo) 3. `display: 'swap'` muestra texto inmediatamente 4. Solo cargas los weights necesarios
Impacto: Eliminé completamente el FOUT (Flash of Unstyled Text). Cumulative Layout Shift bajó a 0.001.
Lo interesante: antes tenía 5 weights de Inter cargando. Solo usaba 3. Esos 2 weights extra sumaban payload innecesario.
3. Route Prefetching: El Superpoder Oculto de Next.js
Next.js precargar rutas automáticamente cuando aparecen en viewport. Pero la configuración por defecto es conservadora.
```jsx import Link from 'next/link'
export default function Navigation() { return ( <nav> <Link href="/about" prefetch={true}> Sobre Nosotros </Link> <Link href="/blog" prefetch={true}> Blog </Link> </nav> ) } ```
Esto precarga las rutas inmediatamente. Cuando el usuario hace click, la navegación es instantánea.
El truco que uso para páginas críticas:
```jsx 'use client' import { useEffect } from 'react' import { useRouter } from 'next/navigation'
export default function HomePage() { const router = useRouter()
useEffect(() => { // Precarga rutas críticas al montar router.prefetch('/pricing') router.prefetch('/demo') }, [router])
return ( // Tu contenido ) } ```
Impacto: La navegación de landing a pricing se siente instantánea. Literalmente 0ms de wait time percibido.
Pero cuidado: no precarges todo. Cada prefetch consume bandwidth. Prioriza las rutas con más tráfico.
4. Bundle Analysis: El Webpack Bundle Analyzer No Miente
Instalé `@next/bundle-analyzer` y descubrí que tenía 300KB de Lodash en mi bundle.
Solo estaba usando `debounce` y `throttle`.
```bash npm install @next/bundle-analyzer ```
```javascript // next.config.js const withBundleAnalyzer = require('@next/bundle-analyzer')({ enabled: process.env.ANALYZE === 'true', })
module.exports = withBundleAnalyzer({ // tu config }) ```
Corre:
```bash ANALYZE=true npm run build ```
El browser abre con un mapa visual de tu bundle. Todo lo que es grande y no reconoces: candidato a eliminar.
Lo que encontré en mi build:
- Lodash: 300KB → reemplacé con funciones nativas
- Moment.js: 230KB → cambié a date-fns
- Unused Tailwind classes: 80KB → configuré purge correctamente
Total eliminado: ~600KB de JavaScript
Impacto: Time to Interactive bajó de 4.1s a 2.3s en 3G.
Lo Que Realmente Importa
Las optimizaciones de Next.js no son magia. Son decisiones específicas sobre qué cargar, cuándo, y cómo.
La mayoría de proyectos tienen quick wins obvios:
1. Configura next/image correctamente (no solo uses el componente) 2. Self-hostea tus fuentes con next/font 3. Prefetch las rutas críticas proactivamente 4. Analiza tu bundle y elimina dependencias innecesarias
No necesitas un rewrite completo. Necesitas medir, encontrar los cuellos de botella, y atacarlos uno por uno.
Mi workflow actual:
1. Build de producción 2. Lighthouse en móvil (no desktop) 3. Bundle analyzer para identificar peso 4. Chrome DevTools → Performance para encontrar bloqueos 5. Fix el problema más grande primero 6. Repeat
Las herramientas están ahí. Next.js te da las APIs. Pero tienes que usarlas activamente.
La diferencia entre un sitio Next.js "funcional" y uno realmente rápido no es el framework. Es configuración deliberada.
---
*¿Quieres ver el bundle analyzer en acción? La próxima semana comparto el análisis completo de un proyecto real con números específicos de cada optimización.*
