Performance & Optimización

👨‍🍳 Chef

Un restaurante lento pierde clientes

Imagina un restaurante con la mejor comida del mundo, pero donde cada platillo tarda 45 minutos. No importa que tan bueno sea, los clientes se van.

Las aplicaciones web son igual. Cada segundo de carga puede costar hasta 7% de conversiones.

Performance no es optimizacion prematura. Es respeto por el tiempo del usuario.


Core Web Vitals: Las metricas que importan

Google mide 3 metricas principales que afectan SEO y experiencia:

┌─────────────────────────────────────────────────────────┐
│                   CORE WEB VITALS                       │
├─────────────────┬─────────────────┬─────────────────────┤
│      LCP        │      INP        │       CLS           │
│   < 2.5s        │    < 200ms      │      < 0.1          │
│                 │                 │                     │
│  Largest        │  Interaction    │  Cumulative         │
│  Contentful     │  to Next        │  Layout             │
│  Paint          │  Paint          │  Shift              │
│                 │                 │                     │
│  "Carga rapida" │ "Responde bien" │ "Estable visualmente│
└─────────────────┴─────────────────┴─────────────────────┘
MetricaQue mideBuenoMalo
LCPCuanto tarda el contenido principal< 2.5s> 4s
INPRespuesta a interacciones< 200ms> 500ms
CLSCuanto salta el layout< 0.1> 0.25

El pipeline del navegador

Entender como renderiza el navegador te ayuda a optimizar.

HTML ──→ DOM
          │
CSS  ──→ CSSOM ──→ Render Tree ──→ Layout ──→ Paint ──→ Composite

Que bloquea el renderizado

  • JavaScript sincrono: Bloquea parsing del HTML
  • CSS en el <head>: Bloquea render (pero necesario)
  • Fonts externas: Pueden causar FOIT/FOUT

JavaScript: El cuello de botella

El Event Loop

┌─────────────────────────────────────────────────┐
│                  CALL STACK                      │
│    (Ejecuta codigo sincrono, uno a la vez)      │
└─────────────────────────────────────────────────┘
         ▲                         │
         │                         ▼
┌────────┴────────┐    ┌─────────────────────────┐
│   TASK QUEUE    │    │    MICROTASK QUEUE      │
│  (setTimeout,   │    │  (Promises, async/await)│
│   eventos)      │    │  (Mayor prioridad)      │
└─────────────────┘    └─────────────────────────┘

Problema: Bloqueo del main thread

// MAL: Bloquea el UI por 500ms
function processData(items) {
  items.forEach(item => heavyCalculation(item));
}

// BIEN: Divide en chunks
async function processDataAsync(items) {
  const chunkSize = 100;
  for (let i = 0; i < items.length; i += chunkSize) {
    const chunk = items.slice(i, i + chunkSize);
    chunk.forEach(item => heavyCalculation(item));
    await new Promise(r => setTimeout(r, 0)); // Yield al browser
  }
}

Optimizacion de bundles

Code Splitting

// Sin splitting: todo carga al inicio
import { Dashboard } from './Dashboard';
import { Admin } from './Admin';
import { Reports } from './Reports';

// Con splitting: carga bajo demanda
const Dashboard = lazy(() => import('./Dashboard'));
const Admin = lazy(() => import('./Admin'));
const Reports = lazy(() => import('./Reports'));

Tree Shaking

// MAL: Importa toda la libreria (100KB)
import _ from 'lodash';
_.debounce(fn, 300);

// BIEN: Solo lo que necesitas (2KB)
import debounce from 'lodash/debounce';
debounce(fn, 300);

Optimizacion de imagenes

Formatos modernos

FormatoUsoAhorro vs JPEG
WebPGeneral, soporte amplio25-35%
AVIFMejor compresion, menos soporte50%+

Responsive images

<picture>
  <source srcset="hero.avif" type="image/avif">
  <source srcset="hero.webp" type="image/webp">
  <img
    src="hero.jpg"
    alt="Hero"
    loading="lazy"
    width="1200"
    height="600"
  >
</picture>

Next.js Image

import Image from 'next/image';

<Image
  src="/hero.jpg"
  alt="Hero"
  width={1200}
  height={600}
  priority // Para imagenes above the fold
  placeholder="blur" // Efecto de carga
/>

Caching estrategico

Niveles de cache

Usuario ──→ Browser Cache ──→ CDN Cache ──→ Server ──→ DB
           (localStorage,     (Edge)        (Redis)
            sessionStorage)

HTTP Cache Headers

Cache-Control: public, max-age=31536000, immutable
                │       │                 │
                │       │                 └─ No revalidar
                │       └─ 1 año en segundos
                └─ CDN puede cachear

Estrategia por tipo de recurso

RecursoCache-ControlPorque
JS/CSS con hashmax-age=31536000, immutableHash cambia si archivo cambia
HTMLno-cacheSiempre validar con server
API dinamicaprivate, max-age=0Datos frescos
Imagenes estaticasmax-age=864001 dia, CDN

Database performance

Indices: La clave

-- Sin indice: Full table scan (lento)
SELECT * FROM users WHERE email = 'test@example.com';
-- Tiempo: 500ms en 1M rows

-- Con indice: Index lookup (rapido)
CREATE INDEX idx_users_email ON users(email);
-- Tiempo: 2ms

EXPLAIN: Entiende tus queries

EXPLAIN ANALYZE SELECT * FROM orders
WHERE user_id = 123 AND created_at > '2024-01-01';

-- Busca:
-- - Seq Scan (malo en tablas grandes)
-- - Index Scan (bueno)
-- - Rows estimados vs reales

El problema N+1

// MAL: N+1 queries
const users = await User.findAll();
for (const user of users) {
  user.orders = await Order.findByUser(user.id); // 1 query por user
}
// 1 + N queries

// BIEN: Eager loading
const users = await User.findAll({
  include: [{ model: Order }]
});
// 1 query con JOIN

Herramientas de profiling

Chrome DevTools

  1. Performance tab: Graba timeline de carga
  2. Network tab: Waterfall de requests
  3. Lighthouse: Auditoria completa

Que buscar en Performance tab

Timeline:
├── Loading (azul): Parsing HTML
├── Scripting (amarillo): JavaScript
├── Rendering (morado): Layout, style
└── Painting (verde): Dibujando pixeles

Si amarillo domina → Optimiza JS
Si morado domina → Reduce reflows

Checklist de performance

Antes del deploy

  • Lighthouse score > 90
  • LCP < 2.5s
  • Bundle size < 200KB (inicial)
  • Imagenes en WebP/AVIF
  • Lazy loading en imagenes below fold
  • Code splitting activo
  • Cache headers configurados

En produccion

  • CDN configurado
  • Gzip/Brotli activo
  • HTTP/2 habilitado
  • Indices en queries lentas
  • Monitoring de Core Web Vitals

Recursos


Practica

-> Auditoria de Performance - Optimiza una app lenta paso a paso