."}},{"@type":"Question","name":"¿Cómo implemento un Service Worker para caching?","acceptedAnswer":{"@type":"Answer","text":"Registra el Service Worker: if ('serviceWorker' in navigator) {\\n navigator.serviceWorker.register('/sw.js');\\n}. Cachea static assets con estrategia cache-first: const CACHE = 'static-v1';\\nconst ASSETS = ['/index.html', '/styles.css', '/app.js'];\\nself.addEventListener('install', (e) => {\\n e.waitUntil(caches.open(CACHE).then(cache => cache.addAll(ASSETS)));\\n});\\nself.addEventListener('fetch', (e) => {\\n e.respondWith(\\n caches.match(e.request).then(response => response || fetch(e.request))\\n );\\n});. Usa network-first para contenido dinámico: self.addEventListener('fetch', (e) => {\\n e.respondWith(\\n fetch(e.request).catch(() => caches.match(e.request))\\n );\\n});. Usa stale-while-revalidate para API responses: self.addEventListener('fetch', (e) => {\\n e.respondWith(\\n caches.open('api-cache').then(cache =>\\n cache.match(e.request).then(cached => {\\n const fetchPromise = fetch(e.request).then(response => {\\n cache.put(e.request, response.clone());\\n return response;\\n });\\n return cached || fetchPromise;\\n })\\n )\\n );\\n});. Limpia caches viejos: self.addEventListener('activate', (e) => {\\n e.waitUntil(\\n caches.keys().then(keys =>\\n Promise.all(keys.filter(k => k !== CACHE).map(k => caches.delete(k)))\\n )\\n );\\n});. Usa Workbox para manejo más fácil de Service Workers: import { registerRoute } from 'workbox-routing';\\nimport { CacheFirst } from 'workbox-strategies';\\nregisterRoute(/\\.(?:css|js)$/, new CacheFirst());."}},{"@type":"Question","name":"¿Cómo optimizo scripts de terceros?","acceptedAnswer":{"@type":"Answer","text":"Carga scripts de terceros asíncronamente: . Defer scripts no críticos: . Usa loading=\"lazy\" para iframes: Skip to content
StackPractices
intermediate Por StackPractices

Optimización de Performance Web

Mejora Core Web Vitals, reduce tamaños de bundle y optimiza performance frontend con lazy loading, code splitting y herramientas de build modernas.

Nota para desarrolladores hispanohablantes: Esta guía incluye ejemplos y convenciones de nomenclatura adaptadas a equipos que trabajan en español. Cuando existen diferencias significativas en terminología técnica entre el inglés y el español, se indican explícitamente para facilitar la comunicación en equipos multiculturales.

Visión General

El performance web impacta directamente el engagement de usuarios, tasas de conversión y rankings de búsqueda. Los Core Web Vitals de Google — Largest Contentful Paint (LCP), Interaction to Next Paint (INP) y Cumulative Layout Shift (CLS) — proveen targets medibles. Este recurso cubre técnicas prácticas: lazy loading, code splitting, optimización de imágenes, critical CSS y modern build tooling para lograr cargas de página bajo 3 segundos.

Cuándo Usar

Usa este recurso cuando:

  • Los scores de Core Web Vitals están fallando (LCP > 2.5s, CLS > 0.1)
  • Usuarios móviles en redes 3G abandonan páginas antes de que carguen
  • Los bundle sizes exceden 200KB e impactan el time-to-interactive
  • Scripts de terceros (analytics, ads) bloquean el main thread

Solución

Critical CSS Inline + Async Load (HTML)

<head>
  <!-- Inline critical CSS (~14KB max) -->
  <style>
    /* Above-fold styles: header, hero, layout skeleton */
    body{margin:0;font-family:system-ui}
    .hero{background:#3b82f6;min-height:60vh}
  </style>

  <!-- Preload key resources -->
  <link rel="preload" href="/fonts/inter.woff2" as="font" type="font/woff2" crossorigin>
  <link rel="preload" href="/hero-image.webp" as="image" fetchpriority="high">

  <!-- Async load non-critical CSS -->
  <link rel="preload" href="/styles.css" as="style" onload="this.rel='stylesheet'">
</head>

Lazy Loading Images con API Nativa

<!-- Native lazy loading — no requiere JavaScript -->
<img src="hero.webp" alt="Hero" fetchpriority="high" width="800" height="400">
<img src="below-fold-1.webp" alt="Product" loading="lazy" width="400" height="300">
<img src="below-fold-2.webp" alt="Team" loading="lazy" width="400" height="300">

Code Splitting con Live Imports (React)

import { lazy, Suspense } from 'react';

const HeavyChart = lazy(() => import('./HeavyChart'));
const AnalyticsDashboard = lazy(() => import('./AnalyticsDashboard'));

function Dashboard() {
  return (
    <div>
      <CriticalStats /> {/* Siempre cargado */}
      <Suspense fallback={<Spinner />}>
        <HeavyChart /> {/* Cargado on demand */}
      </Suspense>
      <Suspense fallback={<Spinner />}>
        <AnalyticsDashboard /> {/* Chunk separado */}
      </Suspense>
    </div>
  );
}

Explicación

Targets de Core Web Vitals:

MétricaBuenoMaloMide
LCP< 2.5s> 4sTiempo de carga del elemento visible más grande
INP< 200ms> 500msResponsividad de interacciones
CLS< 0.1> 0.25Estabilidad visual (layout shifts)
TTFB< 600ms> 1.8sTime to first byte

Ejemplo de performance budget:

  • JavaScript: 150KB (gzipped)
  • Imágenes: 250KB total
  • CSS: 50KB (incluyendo critical inline)
  • Fonts: 40KB (subsetted)
  • Terceros: 100KB máximo

Variantes

TécnicaImpactoEsfuerzo
Optimización de imágenes (WebP/AVIF)-50% bytes de imagenBajo
Font subsetting-80% bytes de fontBajo
Code splitting-60% JS inicialMedio
Edge caching-90% TTFBBajo
Service WorkerVisitas repetidas instantáneasMedio
HTTP/3 + QUICMás rápido en redes con pérdidaBajo (CDN)

Lo que funciona

  • Mide usuarios reales, no tests de lab: Field data de Chrome UX Report refleja condiciones actuales
  • Optimiza el critical path: Cualquier cosa bloqueando <head> debería estar bajo 50KB total. Consulta server-side rendering.
  • Self-host fonts y analytics: Conexiones de terceros agregan overhead de DNS + TLS + TCP
  • Usa content-visibility: auto: Los browsers skip rendering de contenido off-screen
  • Defer JavaScript no crítico: defer o type="module" para scripts no necesarios inmediatamente

Errores Comunes

  1. Imágenes hero oversized: Un PNG hero de 4MB destruye LCP; usa imágenes responsive con srcset
  2. Terceros render-blocking: Google Fonts cargados síncronamente retrasan first paint
  3. Sin resource hints: preload, prefetch y preconnect son wins de performance gratuitos
  4. Hidratar todo: Arquitectura de islands (Astro, Fresh) envía zero JS para contenido estático
  5. Ignorar mobile: 70% de usuarios están en mobile; testea en dispositivos reales, no solo DevTools

Preguntas Frecuentes

P: ¿Cuál es la única mejora de performance más grande? R: Optimización de imágenes. Las imágenes son típicamente 60-80% del peso de página. Usa formatos modernos, sizing responsive y lazy loading.

P: ¿Debería usar un CDN? R: Sí. Un CDN reduce TTFB sirviendo desde edge locations cercanas a los usuarios. Esencial para audiencias globales.

P: ¿Cómo balanceo performance con developer experience? R: Usa frameworks que optimizan por default (Astro, SvelteKit, Next.js con App Router). No luches contra las herramientas.

¿Cómo mido Core Web Vitals en producción?

Usa la librería web-vitals de JavaScript para recolectar métricas de usuarios reales: import { onLCP, onINP, onCLS, onTTFB, onFCP } from 'web-vitals';\nonLCP((metric) => sendToAnalytics('LCP', metric.value));\nonINP((metric) => sendToAnalytics('INP', metric.value));\nonCLS((metric) => sendToAnalytics('CLS', metric.value));. Envía data a tu backend de analytics: function sendToAnalytics(name, value) {\n navigator.sendBeacon('/api/vitals', JSON.stringify({ name, value, page: location.pathname }));\n}. Usa el reporte de Core Web Vitals de Google Search Console para field data en tu sitio. Configura alerts para regresiones: si LCP p75 excede 2.5s, triggera un alert. Usa Lighthouse CI en tu pipeline: lighthouse-ci --assertions.lcp=2.5 --assertions.cls=0.1 --assertions.inp=200. Recolecta métricas por page template, no solo promedios site-wide. Segmenta por device type (mobile, desktop, tablet) y connection type (4G, 3G, WiFi). Usa PerformanceObserver para custom metrics: new PerformanceObserver((list) => {\n for (const entry of list.getEntries()) {\n console.log(${entry.name}: ${entry.startTime}ms);\n }\n}).observe({ entryTypes: ['paint', 'largest-contentful-paint'] });.

¿Cómo optimizo tamaños de bundle de JavaScript?

Analiza tu bundle con webpack-bundle-analyzer o rollup-plugin-visualizer: import { visualizer } from 'rollup-plugin-visualizer';\nexport default {\n plugins: [visualizer({ open: true, filename: 'bundle-stats.html' })]\n};. Identifica dependencias grandes y reemplazalas con alternativas más ligeras: moment.js (280KB) → date-fns (20KB), lodash (70KB) → lodash-es con tree shaking. Usa tree shaking: import { debounce } from 'lodash-es'; en lugar de import _ from 'lodash';. Habilita gzip y brotli compression en tu server: gzip on;\ngzip_types text/css application/javascript; en nginx. Code-split por ruta: const About = lazy(() => import('./About')); para reducir el bundle inicial. Usa dynamic imports para features condicionales: if (supportsWebGL) {\n const { render3D } = await import('./3d-renderer');\n render3D();\n}. Audita paquetes de terceros: npm ls --production y remueve dependencias no usadas. Setea límites de bundle size en CI: maxSize: '150KB' para failear builds que exceden budgets. Usa la extensión import-cost de VS Code para ver tamaños de imports durante development. Considera module federation para micro-frontends para compartir dependencias across apps.

¿Cómo optimizo font loading?

Usa font-display: swap para evitar texto invisible: @font-face {\n font-family: 'Inter';\n src: url('/fonts/inter.woff2') format('woff2');\n font-display: swap;\n}. Preload fonts críticos: <link rel="preload" href="/fonts/inter.woff2" as="font" type="font/woff2" crossorigin>. Subset fonts para incluir solo caracteres usados: pyftsubset inter.ttf --text="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789" --output-file=inter-subset.woff2. Usa variable fonts para reducir file count: un variable font file reemplaza múltiples weight files. Self-host fonts en lugar de usar Google Fonts CDN para evitar overhead de third-party connection. Usa size-adjust en @font-face para matchear métricas de fallback font: @font-face {\n font-family: 'Inter-fallback';\n src: local('Arial');\n size-adjust: 100%;\n}. Monitorea font loading: document.fonts.ready.then(() => {\n console.log('All fonts loaded');\n});. Usa unicode-range para splitear fonts por script: @font-face {\n unicode-range: U+0000-00FF;\n src: url('/fonts/inter-latin.woff2');\n}.

¿Cómo optimizo imágenes para web performance?

Usa formatos modernos: WebP (30% más pequeño que JPEG) y AVIF (50% más pequeño que JPEG). Sirve imágenes responsive con srcset: <img\n src="hero-800.webp"\n srcset="hero-400.webp 400w, hero-800.webp 800w, hero-1200.webp 1200w"\n sizes="(max-width: 600px) 400px, (max-width: 1200px) 800px, 1200px"\n alt="Hero"\n fetchpriority="high"\n>. Usa <picture> para format negotiation: <picture>\n <source type="image/avif" srcset="hero.avif">\n <source type="image/webp" srcset="hero.webp">\n <img src="hero.jpg" alt="Hero">\n</picture>. Comprime imágenes: cwebp -q 80 input.jpg -o output.webp para lossy, cwebp -lossless input.png -o output.webp para lossless. Usa blur placeholders para imágenes above-fold: genera una versión tiny blurred (20px wide) y escalala con CSS filter: blur(20px) hasta que la imagen completa carga. Lazy-load imágenes below-fold: <img loading="lazy" src="...">. Setea width y height explícitos para prevenir CLS. Usa CDN image transformation: https://cdn.example.com/image.jpg?w=800&format=webp para servir versiones optimizadas on the fly. Evita usar imágenes para texto — usa CSS en su lugar. Usa SVG para iconos y logos: <img src="logo.svg" alt="Logo">.

¿Cómo reduzco layout shifts (CLS)?

Reserva espacio para imágenes, ads y embeds: <img width="800" height="400" src="..."> previene que el browser reflowee cuando la imagen carga. Usa CSS aspect-ratio: .video-container {\n aspect-ratio: 16 / 9;\n}. Evita inyectir contenido above contenido existente: banner ads deberían reservar espacio antes de cargar. Usa min-height para áreas de contenido dinámico: .comments {\n min-height: 200px;\n}. Preconnect a origins de terceros: <link rel="preconnect" href="https://cdn.example.com"> para evitar late resource discovery. Usa font-display: optional para fonts no críticos para prevenir font-swap layout shifts. Evita display: none toggling en contenido above-fold. Usa transform y opacity para animaciones en lugar de top, left, width, height — estos no triggerean layout. Setea dimensiones explícitas en iframes: <iframe width="560" height="315" src="...">. Usa content-visibility: auto con contain-intrinsic-size: .card {\n content-visibility: auto;\n contain-intrinsic-size: 200px;\n}.

¿Cómo mejoro Interaction to Next Paint (INP)?

INP mide responsividad a interacciones de usuario. Break long tasks: function processItems(items) {\n // Process in chunks of 50ms\n const chunk = items.slice(0, 50);\n // ... process chunk\n if (items.length > 50) {\n setTimeout(() => processItems(items.slice(50)), 0);\n }\n}. Usa requestIdleCallback para work no urgente: requestIdleCallback(() => {\n // Analytics, reporting, etc.\n});. Debounce scroll y resize handlers: const handleResize = debounce(() => {\n // Expensive layout calculation\n}, 150);\nwindow.addEventListener('resize', handleResize);. Usa scheduler.yield() cuando esté disponible: async function processQueue() {\n for (const item of queue) {\n processItem(item);\n await scheduler.yield(); // Yield to main thread\n }\n}. Evita synchronous layout reads: // Bad: forces layout\nfor (let i = 0; i < items.length; i++) {\n items[i].style.left = ${items[i].offsetLeft + 10}px;\n}\n// Good: batch reads and writes\nconst positions = items.map(item => item.offsetLeft);\nitems.forEach((item, i) => {\n item.style.left = ${positions[i] + 10}px;\n});. Usa Web Workers para CPU-intensive tasks: const worker = new Worker('compute.js');\nworker.postMessage(data);\nworker.onmessage = (e) => updateUI(e.data);. Minimiza JavaScript de terceros que bloquea el main thread. Usa requestAnimationFrame para visual updates: function animate() {\n // Update DOM\n requestAnimationFrame(animate);\n}.

¿Cómo uso resource hints efectivamente?

Usa preload para recursos críticos en la página actual: <link rel="preload" href="/fonts/inter.woff2" as="font" crossorigin>. Usa prefetch para recursos necesarios en la próxima página: <link rel="prefetch" href="/next-page.js">. Usa preconnect para establecer conexiones tempranas: <link rel="preconnect" href="https://api.example.com">. Usa dns-prefetch para optimización solo de DNS: <link rel="dns-prefetch" href="//cdn.example.com">. Usa modulepreload para JavaScript modules: <link rel="modulepreload" href="/app.js">. Prioriza con fetchpriority: <img src="hero.webp" fetchpriority="high"> para above-fold, <img src="below.webp" fetchpriority="low"> para below-fold. Evita overusar preload — cada hint consume bandwidth. Testea con Network tab en DevTools para verificar que los hints funcionan. Usa Speculation Rules API para predictive prefetching: <script type="speculationrules">\n{ "prefetch": [{ "source": "list", "urls": ["/about", "/contact"] }] }\n</script>.

¿Cómo implemento un Service Worker para caching?

Registra el Service Worker: if ('serviceWorker' in navigator) {\n navigator.serviceWorker.register('/sw.js');\n}. Cachea static assets con estrategia cache-first: const CACHE = 'static-v1';\nconst ASSETS = ['/index.html', '/styles.css', '/app.js'];\nself.addEventListener('install', (e) => {\n e.waitUntil(caches.open(CACHE).then(cache => cache.addAll(ASSETS)));\n});\nself.addEventListener('fetch', (e) => {\n e.respondWith(\n caches.match(e.request).then(response => response || fetch(e.request))\n );\n});. Usa network-first para contenido dinámico: self.addEventListener('fetch', (e) => {\n e.respondWith(\n fetch(e.request).catch(() => caches.match(e.request))\n );\n});. Usa stale-while-revalidate para API responses: self.addEventListener('fetch', (e) => {\n e.respondWith(\n caches.open('api-cache').then(cache =>\n cache.match(e.request).then(cached => {\n const fetchPromise = fetch(e.request).then(response => {\n cache.put(e.request, response.clone());\n return response;\n });\n return cached || fetchPromise;\n })\n )\n );\n});. Limpia caches viejos: self.addEventListener('activate', (e) => {\n e.waitUntil(\n caches.keys().then(keys =>\n Promise.all(keys.filter(k => k !== CACHE).map(k => caches.delete(k)))\n )\n );\n});. Usa Workbox para manejo más fácil de Service Workers: import { registerRoute } from 'workbox-routing';\nimport { CacheFirst } from 'workbox-strategies';\nregisterRoute(/\.(?:css|js)$/, new CacheFirst());.

¿Cómo optimizo scripts de terceros?

Carga scripts de terceros asíncronamente: <script src="https://analytics.example.com/js" async></script>. Defer scripts no críticos: <script src="https://widget.example.com/js" defer></script>. Usa loading="lazy" para iframes: <iframe src="https://widget.example.com" loading="lazy">. Self-host scripts de terceros cuando sea posible para evitar DNS lookups adicionales. Usa la librería Partytown para correr scripts de terceros en un Web Worker: <script type="text/partytown" src="https://analytics.example.com/js"></script>. Audita el impacto de terceros con Lighthouse: checkea el audit “Reduce third-party usage”. Setea un timeout para scripts de terceros: const script = document.createElement('script');\nscript.src = 'https://widget.example.com/js';\nscript.async = true;\nsetTimeout(() => {\n if (!window.widgetLoaded) {\n script.remove(); // Remove if not loaded in 3s\n }\n}, 3000);. Usa resource hints para dominios de terceros: <link rel="preconnect" href="https://analytics.example.com">. Monitorea execution time de scripts de terceros con Performance Observer: new PerformanceObserver((list) => {\n for (const entry of list.getEntries()) {\n if (entry.name.includes('third-party.com')) {\n console.log(Third-party: ${entry.name} took ${entry.duration}ms);\n }\n }\n}).observe({ entryTypes: ['resource'] });.

¿Cómo configuro performance budgets?

Define budgets en webpack.config.js: performance: {\n hints: 'warning',\n maxAssetSize: 150000, // 150KB\n maxEntrypointSize: 200000 // 200KB\n}. Usa Lighthouse CI budgets: // lighthouserc.js\nmodule.exports = {\n ci: {\n assert: {\n assertions: {\n 'resource-summary:script:size': ['<', 150000],\n 'resource-summary:stylesheet:size': ['<', 50000],\n 'resource-summary:image:size': ['<', 250000]\n }\n }\n }\n};. Usa size-limit para library bundles: // package.json\n"size-limit": [\n { "path": "dist/index.js", "limit": "10KB" }\n]. Monitorea budgets en CI: npx size-limit failea el build si se excede. Trackea budgets over time con Bundlephobia o Bundle Analyzer. Setea budgets por ruta, no solo site-wide. Incluye scripts de terceros en budgets. Revisa budgets trimestralmente y ajusta según sea necesario.

¿Cómo optimizo CSS delivery?

Inlina critical CSS en <head>: <style>/* above-fold styles */</style>. Carga CSS no crítico asíncronamente: <link rel="preload" href="/styles.css" as="style" onload="this.rel='stylesheet'">. Usa media attribute para CSS condicional: <link rel="stylesheet" href="print.css" media="print">. Remueve CSS no usado con PurgeCSS: import PurgeCSS from 'purgecss';\\nconst purgeCSSResults = await new PurgeCSS().purge({\\n content: ['**/*.html'],\\n css: ['**/*.css']\\n});. Usa CSS containment: .widget {\\n contain: layout style paint;\\n} para isolatar rendering. Evita @import en CSS — bloquea rendering. Usa CSS custom properties para theming en lugar de múltiples stylesheets. Minifica CSS: cssnano para PostCSS o css-minimizer-webpack-plugin para webpack. Splitea CSS por ruta: import './about.css'; en el componente About. Usa content-visibility: auto para secciones below-fold. Evita selectores costosos: * { } y selectores anidados como .container > .row > .col > .card > .title son lentos. Usa BEM o utility classes para flatter specificity. Prefiere transform y opacity para animaciones — son compositor-only properties. Usa will-change con moderación: will-change: transform hints al browser optimizar, pero overuse desperdicia memoria.

¿Cómo optimizo para redes móviles?

Testea en conexiones 3G reales: Chrome DevTools network throttling en “Slow 3G” (400ms RTT, 500KB/s). Usa adaptive loading: if (navigator.connection) {\\n const effectiveType = navigator.connection.effectiveType;\\n if (effectiveType === '2g' || effectiveType === 'slow-2g') {\\n // Load low-res images, disable video autoplay\\n }\\n}. Sirve imágenes más pequeñas en mobile: <img srcset=\"image-400.webp 400w, image-800.webp 800w\" sizes=\"(max-width: 600px) 400px, 800px\">. Reduce JavaScript en mobile: CPUs móviles son 4-10x más lentas que desktop. Usa header Save-Data: if (navigator.connection.saveData) {\\n // Skip loading non-essential resources\\n}. Prioriza contenido above-fold: inlina critical CSS, lazy-load imágenes below-fold. Usa HTTP/2 o HTTP/3 para multiplexing: múltiples requests sobre una sola conexión. Preconnect a origins críticos: <link rel="preconnect" href="https://api.example.com">. Minimiza redirects: cada redirect añade RTT. Usa resource hints para navigation: <link rel="prerender" href="/next-page"> (deprecated, usa Speculation Rules API en su lugar). Cachea agresivamente con Service Workers: estrategias offline-first para visitas repetidas. Monitorea real user metrics: field data revela performance móvil real, no simulaciones de lab.

¿Cómo optimizo server response time (TTFB)?

Usa edge computing: Cloudflare Workers, Vercel Edge Functions, o Deno Deploy para servir contenido cerca de usuarios. Cachea en el edge: Cache-Control: public, max-age=3600 para static assets. Usa CDN para contenido dinámico: algunos CDNs cachean dynamic responses con TTLs cortos. Optimiza database queries: añade indexes, usa connection pooling, cachea queries frecuentes. Usa SSR caching: cachea rendered HTML con Cache-Control: s-maxage=600, stale-while-revalidate=60. Usa incremental static regeneration (ISR): export async function getStaticProps() {\\n return {\\n props: { data },\\n revalidate: 60 // Regenerate every 60 seconds\\n };\\n}. Evita synchronous third-party API calls en el request path: usa webhooks o background jobs. Usa HTTP caching headers: ETag, Last-Modified, y Cache-Control. Comprime HTML: gzip on;\\ngzip_types text/html; en nginx. Usa HTTP/2 push para recursos críticos (deprecated en Chrome, pero todavía funciona en algunos browsers). Monitorea TTFB con PerformanceObserver: new PerformanceObserver((list) => {\\n for (const entry of list.getEntries()) {\\n if (entry.entryType === 'navigation') {\\n console.log(TTFB: ${entry.responseStart - entry.requestStart}ms);\\n }\\n }\\n}).observe({ entryTypes: ['navigation'] });. Usa origin shield: una CDN layer que reduce load en tu origin server. Optimiza TLS handshake: usa TLS 1.3 (1-RTT vs 2-RTT para TLS 1.2), OCSP stapling, y session resumption.

¿Cómo manejo performance para single-page applications?

Implementa route-level code splitting: const routes = [\\n { path: '/', component: () => import('./Home') },\\n { path: '/about', component: () => import('./About') }\\n];. Prefetch rutas probables: router.afterEach((to) => {\\n if (to.path === '/') {\\n import('./About'); // Prefetch About component\\n }\\n});. Usa skeleton screens en lugar de spinners: .skeleton {\\n background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%);\\n background-size: 200% 100%;\\n animation: shimmer 1.5s infinite;\\n}. Implementa virtual scrolling para listas largas: import { FixedSizeList } from 'react-window';\\n<FixedSizeList height={600} itemCount={10000} itemSize={35}>\\n {Row}\\n</FixedSizeList>. Debounce search inputs: const debouncedSearch = useMemo(() => debounce(fetchResults, 300), []);. Memoiza computations costosas: const sortedItems = useMemo(() => items.sort(compareFn), [items]);. Usa React.lazy con error boundaries: <ErrorBoundary>\\n <Suspense fallback={<Skeleton />}>\\n <LazyComponent />\\n </Suspense>\\n</ErrorBoundary>. Evita re-renders innecesarios: usa React.memo para pure components, useCallback para event handlers. Usa useDeferredValue para updates no urgentes: const deferredQuery = useDeferredValue(query);. Implementa progressive hydration: hidrata componentes above-fold primero, defer below-fold. Usa startTransition para state updates de low-priority: startTransition(() => {\\n setFilterValue(value);\\n});.

¿Cómo mido y optimizo Time to First Byte (TTFB)?

TTFB mide el tiempo desde navigation start hasta el primer byte de la response. Target: bajo 600ms para bueno, bajo 200ms para excelente. Mide con Navigation Timing API: const timing = performance.getEntriesByType('navigation')[0];\\nconst ttfb = timing.responseStart - timing.requestStart;\\nconsole.log(TTFB: ${ttfb}ms);. Optimiza DNS resolution: usa DNS prefetching <link rel="dns-prefetch" href="//api.example.com">. Reduce connection time: usa preconnect <link rel="preconnect" href="https://api.example.com">. Usa HTTP/2 o HTTP/3: multiplexing elimina connection overhead para múltiples resources. Optimiza server processing: cachea database queries, usa connection pooling, añade Redis caching. Usa un CDN: edge servers reducen distancia física a usuarios. Habilita compression: gzip o brotli para HTML responses. Usa server-side caching: Cache-Control: public, max-age=300, s-maxage=600. Monitorea TTFB por región: usuarios en diferentes locations geográficas experimentan diferente TTFB. Usa synthetic monitoring: corre Lighthouse desde múltiples regiones. Configura alerts: si TTFB p75 excede 600ms, investiga. Usa origin shield: reduce load en origin cacheando en CDN edge. Optimiza TLS: usa TLS 1.3, habilita OCSP stapling, usa session resumption. Reduce redirect chains: cada redirect añade un RTT. Usa 103 Early Hints para empezar a cargar recursos antes de que la response completa esté lista: Link: </styles.css>; rel=preload; as=style.

¿Cómo uso Lighthouse CI para performance testing automatizado?

Instala Lighthouse CI: npm install -g @lhci/cli. Configura en lighthouserc.js: module.exports = {\\n ci: {\\n collect: {\\n url: ['https://example.com', 'https://example.com/about'],\\n numberOfRuns: 3,\\n settings: {\\n preset: 'desktop',\\n throttling: { rttMs: 40, throughputKbps: 10240 }\\n }\\n },\\n assert: {\\n assertions: {\\n 'categories:performance': ['warn', { minScore: 0.8 }],\\n 'first-contentful-paint': ['error', { maxNumericValue: 2000 }],\\n 'largest-contentful-paint': ['error', { maxNumericValue: 2500 }],\\n 'cumulative-layout-shift': ['error', { maxNumericValue: 0.1 }],\\n 'total-blocking-time': ['error', { maxNumericValue: 300 }]\\n }\\n },\\n upload: {\\n target: 'temporary-public-storage'\\n }\\n }\\n};. Corre en CI: lhci autorun. Configura GitHub Actions: name: Lighthouse CI\\non: [pull_request]\\njobs:\\n lighthouse:\\n runs-on: ubuntu-latest\\n steps:\\n - uses: actions/checkout@v4\\n - run: npm install && npm run build\\n - run: npm install -g @lhci/cli\\n - run: lhci autorun || true\\n - uses: actions/upload-artifact@v4\\n with:\\n name: lighthouse-report\\n path: .lighthouseci/. Compara resultados contra baseline: lhci diff. Usa Lighthouse Server para historical data: lhci server --storage.storageMethod=sql. Setea performance budgets por ruta. Testea en presets mobile y desktop. Usa --collect.url para múltiples páginas. Asserta contra métricas específicas: LCP, CLS, TBT, FCP, TTI. Genera HTML reports: lhci report --report=html.

¿Cómo optimizo hydration en SSR frameworks?

Hydration es el proceso de attachar event listeners a server-rendered HTML. Usa partial hydration: Astro Islands hidrata solo componentes interactivos, dejando static HTML como zero-JS. Usa progressive hydration: hidrata componentes above-fold primero, defer below-fold: // Astro\\n<Counter client:load /> {/* Hydrate immediately */}\\n<HeavyChart client:visible /> {/* Hydrate when visible */}\\n<Comments client:idle /> {/* Hydrate when browser is idle */}. Usa client:visible para hidratar on scroll into view: ahorra JS execution para contenido below-fold. Usa client:idle para componentes no críticos: requestIdleCallback delays hydration. Usa client:media para componentes responsive: <MobileNav client:media="(max-width: 768px)" /> solo hidrata en mobile. Evita hydration mismatches: server y client deben renderizar HTML idéntico. Usa useId() para stable IDs across server y client. Defer hydration en conexiones lentas: if (navigator.connection?.effectiveType === '4g') {\\n hydrateRoot(container, <App />);\\n}. Usa React Server Components: zero client JS para server-only components. Mide hydration performance: performance.mark('hydration-start');\\nhydrateRoot(container, <App />);\\nperformance.mark('hydration-end');\\nperformance.measure('hydration', 'hydration-start', 'hydration-end');. Usa requestIdleCallback para non-critical hydration: requestIdleCallback(() => {\\n hydrateRoot(container, <App />);\\n});. Evita large hydration trees: splitea en islands más pequeños. Usa streaming SSR: renderToPipeableStream en React 18 envía HTML en chunks, permitiendo al browser pintar progresivamente.

¿Cómo optimizo web fonts para performance?

Usa font-display: swap para mostrar fallback text inmediatamente: @font-face {\\n font-family: 'Inter';\\n src: url('/fonts/inter.woff2') format('woff2');\\n font-display: swap;\\n}. Usa font-display: optional para fonts no críticos: da un window de 100ms para cargar, luego falla permanentemente a fallback para evitar late text swaps. Preload fonts críticos: <link rel="preload" href="/fonts/inter.woff2" as="font" type="font/woff2" crossorigin>. Subset fonts para reducir file size: pyftsubset inter.ttf --output-file=inter-subset.woff2 --format=woff2 --unicodes="U+0000-00FF" para Latin only. Usa variable fonts: un file cubre todos los weights (100-900), reduciendo total font payload. Self-host fonts: Google Fonts CDN añade un DNS lookup, TCP connection, y TLS handshake. Usa unicode-range para splitear fonts por script: @font-face {\\n font-family: 'NotoSans';\\n src: url('/fonts/noto-latin.woff2');\\n unicode-range: U+0000-00FF;\\n}\\n@font-face {\\n font-family: 'NotoSans';\\n src: url('/fonts/noto-cyrillic.woff2');\\n unicode-range: U+0400-04FF;\\n}. Usa size-adjust para matchear métricas de fallback font: @font-face {\\n font-family: 'Inter-fallback';\\n src: local('Arial');\\n size-adjust: 100.06%;\\n ascent-override: 95%;\\n descent-override: 22%;\\n line-gap-override: 0%;\\n}. Monitorea font loading: document.fonts.load('16px Inter').then(() => {\\n console.log('Inter loaded');\\n});. Usa font-display: block solo para icon fonts donde mostrar fallback characters es peor que no mostrar texto. Usa WOFF2 format: 30% más pequeño que WOFF, ampliamente soportado. Usa servicios de font subsetting: Google Fonts API soporta parámetro text para dynamic subsetting.

¿Cómo implemento edge-side rendering (ESR)?

Edge-side rendering mueve SSR a CDN edge locations, reduciendo TTFB para usuarios worldwide. Usa Cloudflare Workers: export default {\\n async fetch(request, env) {\\n const html = await renderApp(request);\\n return new Response(html, {\\n headers: { 'Content-Type': 'text/html' }\\n });\\n }\\n};. Usa Vercel Edge Functions: export default function handler(req, res) {\\n const html = renderApp(req);\\n res.setHeader('Content-Type', 'text/html');\\n res.send(html);\\n}. Usa Deno Deploy: Deno.serve(async (req) => {\\n const html = await renderApp(req);\\n return new Response(html, {\\n headers: { 'Content-Type': 'text/html' }\\n });\\n});. Cachea rendered HTML en el edge: Cache-Control: public, s-maxage=3600, stale-while-revalidate=60. Usa streaming para progressive rendering: return new Response(\\n new ReadableStream({\\n start(controller) {\\n controller.enqueue(encoder.encode('<html><head>...</head><body>'));\\n controller.enqueue(encoder.encode(renderHeader()));\\n controller.enqueue(encoder.encode(renderMain()));\\n controller.enqueue(encoder.encode('</body></html>'));\\n controller.close();\\n }\\n }),\\n { headers: { 'Content-Type': 'text/html' } }\\n);. Usa edge data stores: Cloudflare KV, Vercel Edge Config, Deno KV para low-latency data access. Maneja authentication en el edge: verifica JWTs en el Worker sin llamar al origin. Usa edge middleware para A/B testing: const variant = Math.random() < 0.5 ? 'A' : 'B';\\nconst response = await fetch(request);\\nconst html = await response.text();\\nreturn new Response(html.replace('{{variant}}', variant), response);. Monitorea edge performance: mide TTFB desde múltiples regiones. Usa edge-side includes (ESI) para partial caching: <esi:include src="/header" /> permite cachear fragments independientemente.

¿Cómo optimizo API calls desde el frontend?

Batchea API requests: const fetchBatch = async (ids) => {\\n const response = await fetch('/api/items?ids=' + ids.join(','));\\n return response.json();\\n}; en lugar de requests individuales. Usa request deduplication: const cache = new Map();\\nasync function fetchUser(id) {\\n if (cache.has(id)) return cache.get(id);\\n const promise = fetch(/api/users/${id}).then(r => r.json());\\n cache.set(id, promise);\\n return promise;\\n}. Usa SWR o React Query para caching: const { data } = useSWR('/api/users', fetcher); automáticamente deduplica y cachea. Debounce search API calls: const debouncedFetch = debounce((query) => fetchResults(query), 300);. Usa pagination en lugar de cargar toda la data: const response = await fetch('/api/items?page=1&limit=20');. Usa GraphQL para precise data fetching: requestea solo los fields necesarios en lugar de over-fetching. Implementa optimistic updates: mutate('/api/items', [...items, newItem], false); actualiza UI antes de que el server confirme. Usa stale-while-revalidate: muestra cached data inmediatamente, revalida en background. Cancela in-flight requests: const controller = new AbortController();\\nfetch('/api/search?q=' + query, { signal: controller.signal });\\ncontroller.abort(); // Cancel on new search. Usa IntersectionObserver para infinite scroll: const observer = new IntersectionObserver((entries) => {\\n if (entries[0].isIntersecting) {\\n loadMore();\\n }\\n});\\nobserver.observe(document.querySelector('.sentinel'));. Preload API data: <link rel="preload" href="/api/featured" as="fetch" crossorigin>. Usa stale-while-revalidate cache headers: Cache-Control: max-age=60, stale-while-revalidate=600. Comprime API responses: Accept-Encoding: gzip, br. Usa HTTP/2 multiplexing: múltiples API calls sobre una conexión. Monitorea API call waterfall con Chrome DevTools Network tab.

¿Cómo implemento progressive loading strategies?

Usa skeleton screens: .skeleton {\\n background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%);\\n background-size: 200% 100%;\\n animation: shimmer 1.5s infinite;\\n}\\n@keyframes shimmer {\\n 0% { background-position: 200% 0; }\\n 100% { background-position: -200% 0; }\\n}. Usa blur-up image loading: img.lazy {\\n filter: blur(20px);\\n transition: filter 0.3s;\\n}\\nimg.lazy.loaded {\\n filter: blur(0);\\n}. Implementa progressive JPEG: cjpeg -quality 60 -progressive input.jpg > output.jpg renderiza top-to-bottom mientras carga. Usa <link rel="modulepreload"> para JavaScript modules: import('./chart.js') preloads el module sin ejecutarlo. Usa requestIdleCallback para work no crítico: requestIdleCallback(() => {\\n // Prefetch next page data\\n fetch('/api/next-page-data');\\n});. Implementa route prefetching on hover: link.addEventListener('mouseenter', () => {\\n import('./NextPage');\\n});. Usa prefetch para recursos probables: <link rel="prefetch" href="/next-page.js">. Usa prerender para próximas páginas críticas: <link rel="prerender" href="/checkout"> (deprecated, usa Speculation Rules API). Usa streaming HTML: renderToPipeableStream en React 18 envía HTML en chunks. Usa content-visibility: auto para secciones below-fold: .section {\\n content-visibility: auto;\\n contain-intrinsic-size: 500px;\\n}. Implementa lazy hydration: hidrata componentes cuando entran al viewport. Usa loading="lazy" para imágenes e iframes. Defer offscreen images: const observer = new IntersectionObserver((entries) => {\\n entries.forEach(entry => {\\n if (entry.isIntersecting) {\\n entry.target.src = entry.target.dataset.src;\\n observer.unobserve(entry.target);\\n }\\n });\\n});\\ndocument.querySelectorAll('img[data-src]').forEach(img => observer.observe(img));. Usa fetchpriority="low" para recursos below-fold. Prioriza LCP element: <img src="hero.webp" fetchpriority="high">.

¿Cómo monitoreal real user performance (RUM)?

Usa la librería web-vitals: import { onLCP, onINP, onCLS, onTTFB, onFCP } from 'web-vitals';\\nonLCP(console.log);\\nonINP(console.log);\\nonCLS(console.log);. Envía a analytics: function sendToAnalytics(metric) {\\n const body = JSON.stringify({\\n name: metric.name,\\n value: metric.value,\\n id: metric.id,\\n page: location.pathname,\\n userAgent: navigator.userAgent\\n });\\n navigator.sendBeacon('/api/rum', body);\\n}. Usa Google Analytics 4 events: gtag('event', 'web_vitals', {\\n metric_name: metric.name,\\n metric_value: metric.value,\\n metric_id: metric.id\\n});. Usa PerformanceObserver para custom metrics: new PerformanceObserver((list) => {\\n for (const entry of list.getEntries()) {\\n console.log(entry.name, entry.duration);\\n }\\n}).observe({ entryTypes: ['measure', 'paint', 'largest-contentful-paint'] });. Trackea page load: window.addEventListener('load', () => {\\n const timing = performance.getEntriesByType('navigation')[0];\\n console.log('DOM Content Loaded:', timing.domContentLoadedEventEnd - timing.startTime);\\n console.log('Load Complete:', timing.loadEventEnd - timing.startTime);\\n});. Trackea resource loading: new PerformanceObserver((list) => {\\n for (const entry of list.getEntries()) {\\n if (entry.transferSize > 50000) {\\n console.log(Large resource: ${entry.name} (${entry.transferSize} bytes));\\n }\\n }\\n}).observe({ entryTypes: ['resource'] });. Usa Performance.mark y Performance.measure para custom timing: performance.mark('start-fetch');\\nfetch('/api/data').then(() => {\\n performance.mark('end-fetch');\\n performance.measure('fetch-duration', 'start-fetch', 'end-fetch');\\n});. Segmenta data por device, connection, y page. Usa p75 como la métrica primaria: 75% de usuarios experimentan esto o mejor. Configura alerts para regresiones: si p75 LCP excede 2.5s, notifica al team. Usa Chrome UX Report para field data: https://api.crux-report.com/ para real-world performance data.

¿Cómo optimizo contenido de video para performance?

Usa lazy loading para video: <video loading="lazy" src="hero.mp4" poster="poster.webp"></video>. Usa preload="none" para videos below-fold: <video preload="none" src="intro.mp4"></video>. Usa preload="metadata" para previews de video: <video preload="metadata" src="trailer.mp4"></video> carga solo metadata, no el video completo. Usa poster images: <video poster="poster.webp" src="video.mp4"> muestra una imagen mientras el video carga. Sirve video responsive: <video>\\n <source src="video-720.webm" media="(max-width: 720px)">\\n <source src="video-1080.webm" media="(min-width: 721px)">\\n</video>. Usa adaptive streaming: HLS o DASH ajusta quality basado en bandwidth: <video src="playlist.m3u8" controls></video>. Comprime video: ffmpeg -i input.mp4 -c:v libx264 -crf 28 -preset slow output.mp4 para output web-optimized. Usa codecs modernos: AV1 (30% más pequeño que H.265), VP9 (30% más pequeño que H.264). Usa autoplay="muted" para background videos: <video autoplay muted loop playsinline src="bg.mp4"> — muted autoplay es permitido por browsers. Evita autoplay con sonido: browsers lo bloquean y degrada UX. Usa playsinline para mobile: previene fullscreen playback en iOS. Hostea videos en un CDN: video files son grandes y se benefician de edge caching. Usa disablepictureinpicture para videos no esenciales: el attribute disablepictureinpicture reduce resource usage. Monitorea video loading: const video = document.querySelector('video');\\nvideo.addEventListener('loadeddata', () => console.log('Video ready'));\\nvideo.addEventListener('waiting', () => console.log('Buffering'));. Usa preload="auto" solo para hero videos above-fold. Considera reemplazar background videos con animated images o CSS animations para dispositivos móviles.

¿Cómo optimizo Core Web Vitals en WordPress?

Usa caching plugins: WP Rocket o W3 Total Cache para page caching, object caching, y minification. Optimiza imágenes con Smush o ShortPixel: automatic WebP conversion y lazy loading. Usa critical CSS plugins: Autoptimize o WP Rocket Critical CSS inlina above-fold styles. Minifica CSS y JS: Autoptimize o Fast Velocity Minify. Usa un CDN: Cloudflare o BunnyCDN para edge caching. Limita plugins: cada plugin añade JS/CSS overhead. Usa un theme lightweight: GeneratePress, Astra, o Kadence son performance-optimized. Habilita Gzip compression: AddOutputFilterByType DEFLATE text/html text/css application/javascript en .htaccess. Habilita browser caching: ExpiresActive On\\nExpiresByType text/css "access plus 1 month"\\nExpiresByType application/javascript "access plus 1 month" en .htaccess. Usa database optimization: WP-Optimize limpia post revisions, transients, y spam. Defer JavaScript: add_filter('script_loader_tag', function($tag) {\\n return str_replace(' src', ' defer src', $tag);\\n}); en functions.php. Remueve CSS no usado: add_filter('style_loader_tag', function($tag) {\\n return str_replace(' href', ' media=\"print\" onload=\"this.media=\\'all\\'\" href', $tag);\\n});. Usa server-side rendering: WordPress es SSR por default, asegúrate de que tu theme no lo rompa. Optimiza web fonts: self-host fonts con @font-face y font-display: swap. Usa loading="lazy" para imágenes: WordPress 5.5+ lo añade automáticamente. Limita external scripts: Google Analytics, Facebook Pixel, etc. añaden overhead. Usa wp_enqueue_script con defer o async: wp_enqueue_script('my-script', 'url', [], '1.0', true); el parámetro true carga en footer.

¿Cómo optimizo para accessibility sin sacrificar performance?

Usa semantic HTML: <nav>, <main>, <article>, <section> son gratuitos — no necesitan JS o CSS. Usa aria-label con moderación: solo cuando semantic HTML es insuficiente. Evita ARIA overlays: role="button" en un <div> requiere JavaScript para keyboard handling. Usa <button> en su lugar. Usa prefers-reduced-motion: @media (prefers-reduced-motion: reduce) {\\n * {\\n animation-duration: 0.01ms !important;\\n transition-duration: 0.01ms !important;\\n }\\n}. Usa prefers-color-scheme: @media (prefers-color-scheme: dark) {\\n body { background: #1a1a1a; color: #e0e0e0; }\\n}. Usa prefers-contrast: @media (prefers-contrast: high) {\\n .text { color: #000; }\\n}. Usa focus-visible: :focus-visible {\\n outline: 2px solid #3b82f6;\\n outline-offset: 2px;\\n} para keyboard-only focus styles. Usa skip-to-content links: <a href="#main" class="skip-link">Skip to content</a> con .skip-link { position: absolute; top: -40px; }\\n.skip-link:focus { top: 0; }. Usa alt text para imágenes: <img src="chart.png" alt="Sales increased 20% from Q1 to Q2">. Usa lang attribute: <html lang="en"> ayuda a screen readers. Usa tabindex correctamente: tabindex="0" para focusable elements, tabindex="-1" para remover del tab order. Evita tabindex values > 0. Usa color contrast checkers: WCAG AA requiere 4.5:1 para texto normal, 3:1 para texto grande. Usa prefers-reduced-data: @media (prefers-reduced-data: reduce) {\\n img { content: url('placeholder.png'); }\\n} para servir assets más pequeños. Usa loading="lazy" para contenido below-fold: mejora tanto performance como screen reader experience. Usa describedby para widgets complejos: aria-describedby="help-text" provee context adicional. Testea con keyboard navigation: Tab, Shift+Tab, Enter, Space, Escape deberían funcionar. Usa role="status" para live regions: <div role="status" aria-live="polite">Loading...</div> anuncia updates sin interrumpir.

¿Cómo optimizo build performance?

Usa incremental builds: webpack --watch o vite build --watch solo reconstruye módulos cambiados. Usa build caching: webpack cache: { type: 'filesystem' } o vite built-in cache. Usa esbuild para transpilation: esbuild-loader es 10-100x más rápido que babel-loader. Usa SWC para compilation: @swc/core es Rust-based y considerablemente más rápido que Babel. Usa thread-loader: module: { rules: [{ test: /\\.js$/, use: ['thread-loader', 'babel-loader'] }] } para parallel processing. Usa terser para minification: optimization: { minimizer: [new TerserPlugin({ parallel: true })] }. Usa splitChunks para caching óptimo: optimization: {\\n splitChunks: {\\n chunks: 'all',\\n cacheGroups: {\\n vendor: { test: /[\\\\/]node_modules[\\\\/]/, name: 'vendor' }\\n }\\n }\\n}. Usa externals para CDN-loaded libraries: externals: { react: 'React', 'react-dom': 'ReactDOM' }. Usa tree shaking: mode: 'production' con ES modules habilita dead code elimination. Usa sideEffects: false en package.json: "sideEffects": false le dice a bundlers que todos los files son side-effect free. Usa persistent caching: cache: { type: 'filesystem', buildDependencies: { config: [__filename] } }. Usa build: { target: 'esnext' } en Vite para browsers modernos solo. Usa esbuild para development builds: Vite usa esbuild para dev que es near-instant. Usa rollup-plugin-visualizer para identificar chunks grandes. Usa speed-measure-webpack-plugin para identificar loaders lentos: const smp = new SpeedMeasurePlugin();\\nmodule.exports = smp.wrap(config);. Usa fork-ts-checker-webpack-plugin para type checking en paralelo: type checking no bloquea compilation. Usa module federation para micro-frontends: shared dependencies reducen build time y bundle size. Usa swc-loader en lugar de babel-loader: SWC es 20x más rápido para TypeScript transpilation. Usa lightningcss en lugar de postcss para CSS minification: Rust-based, 100x más rápido.

¿Cómo optimizo JavaScript execution performance?

Usa requestAnimationFrame para visual updates: function animate() {\\n element.style.transform = translateX(${pos}px);\\n requestAnimationFrame(animate);\\n}\\nrequestAnimationFrame(animate);. Usa requestIdleCallback para work no visual: requestIdleCallback(() => {\\n // Process data, send analytics\\n});. Debounce scroll y resize handlers: window.addEventListener('scroll', debounce(handler, 16));. Usa passive event listeners: window.addEventListener('scroll', handler, { passive: true }); le dice al browser que el handler no va a llamar preventDefault(). Usa Web Workers para CPU-intensive tasks: const worker = new Worker('compute.js');\\nworker.postMessage(data);\\nworker.onmessage = (e) => console.log(e.data);. Usa OffscreenCanvas para canvas rendering en un Worker: const canvas = element.transferControlToOffscreen();\\nconst worker = new Worker('renderer.js');\\nworker.postMessage({ canvas }, [canvas]);. Usa SharedArrayBuffer para zero-copy data sharing entre threads. Usa ArrayBuffer y typed arrays para numerical data: const floats = new Float32Array(1000) es más rápido que const floats = new Array(1000). Usa structuredClone para deep cloning: const copy = structuredClone(obj) es más rápido que JSON.parse(JSON.stringify(obj)). Usa Object.freeze para immutable objects: frozen objects permiten a engines optimizar property access. Usa Map y Set para frequent lookups: const set = new Set([1, 2, 3]); set.has(2) es O(1) vs Array.includes O(n). Usa WeakMap para DOM-related metadata: const meta = new WeakMap(); meta.set(element, { data }); permite garbage collection cuando el element se remueve. Evita delete en objects: deoptimiza hidden classes. Setea a undefined en su lugar: obj.prop = undefined. Usa for...of o forEach para iteration: más rápido que for...in que itera sobre prototype chain. Usa Array.from para convertir iterables: Array.from(document.querySelectorAll('.item')). Usa Promise.all para parallel async operations: const [users, posts] = await Promise.all([fetchUsers(), fetchPosts()]);. Usa AbortController para cancellable fetches. Usa queueMicrotask para microtask scheduling: queueMicrotask(() => console.log('microtask'));. Usa performance.now() para high-resolution timing: const start = performance.now(); doWork(); console.log(performance.now() - start);.

¿Cómo optimizo rendering performance?

Usa CSS transform y opacity para animaciones: son compositor-only properties que no triggerean layout o paint. Evita animar width, height, top, left, margin, padding — triggerean layout recalculations. Usa will-change con moderación: .animated { will-change: transform; } le hintea al browser crear una layer separada, pero overuse desperdicia memoria. Usa contain: strict para componentes aislados: .widget { contain: strict; } previene que los cambios del componente afecten el resto de la page. Usa content-visibility: auto para secciones below-fold: .section { content-visibility: auto; contain-intrinsic-size: 1000px; } salta rendering hasta que sea visible. Usa contain: layout style paint para widgets complejos. Reduce reflows: batchea DOM writes: element.style.cssText = 'width: 100px; height: 50px;'; en lugar de cambios individuales de property. Usa DocumentFragment para batch DOM insertions: const fragment = document.createDocumentFragment();\\nitems.forEach(item => {\\n const li = document.createElement('li');\\n li.textContent = item;\\n fragment.appendChild(li);\\n});\\nlist.appendChild(fragment);. Usa requestAnimationFrame para visual updates: asegura que los updates happen antes de paint. Usa ResizeObserver en lugar de resize events: const observer = new ResizeObserver(entries => {\\n for (const entry of entries) {\\n console.log(entry.contentRect.width);\\n }\\n});\\nobserver.observe(element);. Usa IntersectionObserver para lazy loading e infinite scroll. Evita forced synchronous layout: element.style.width = '100px';\\nconst width = element.offsetWidth; // Forces layout\\nconsole.log(width);. Lee layout properties antes de escribir: const width = element.offsetWidth;\\nelement.style.width = width + 10 + 'px';. Usa display: none en lugar de visibility: hidden para elementos que no necesitan rendering: display: none remueve del render tree, visibility: hidden todavía renderiza. Usa position: fixed o position: absolute para elementos animados: no afectan surrounding layout. Usa pointer-events: none para elementos decorativos: reduce hit-testing overhead. Usa backface-visibility: hidden para 3D transforms: .card { backface-visibility: hidden; } promueve a su propia layer. Usa transform: translateZ(0) como hack de layer promotion para browsers más antiguos. Usa Chrome DevTools “Performance” tab para identificar layout thrashing y long tasks.

¿Cómo optimizo memory usage?

Detecta memory leaks con Chrome DevTools “Memory” tab: toma heap snapshots, compáralos, y busca retained objects. Usa WeakMap y WeakSet para referencias que no deberían prevenir garbage collection: const cache = new WeakMap(); cache.set(element, data);. Remueve event listeners: element.removeEventListener('click', handler); cuando los elementos se remueven del DOM. Usa AbortController para fetch cancellation: const controller = new AbortController();\\nfetch(url, { signal: controller.signal });\\ncontroller.abort(); cancela el request y libera memoria. Limpia intervals y timeouts: clearInterval(intervalId); clearTimeout(timeoutId);. Usa FinalizationRegistry para cleanup: const registry = new FinalizationRegistry((heldValue) => {\\n console.log(Cleaned up: ${heldValue});\\n});\\nregistry.register(obj, 'my-object');. Usa WeakRef para caches: const cache = new Map();\\nconst ref = new WeakRef(obj);\\nif (ref.deref()) { /* object still alive */ }. Evita closures que capturen objetos grandes: function createHandler() {\\n const huge = new Array(1000000);\\n return () => console.log(huge.length); // huge is retained\\n}. Usa object pooling para objetos frecuentemente creados/destruidos: const pool = [];\\nfunction acquire() { return pool.pop() || new Particle(); }\\nfunction release(p) { pool.push(p); }. Usa structuredClone para deep cloning en lugar de JSON parse/stringify: más rápido y maneja más types. Detach ArrayBuffers después de usar: const buffer = new ArrayBuffer(1024);\\nstructuredClone(buffer, { transfer: [buffer] }); transfiere ownership. Usa performance.memory (Chrome only) para monitorear heap: console.log(performance.memory.usedJSHeapSize);. Usa IntersectionObserver para unload componentes below-fold: disconnect observers cuando los componentes se unmount. Usa disconnect() en observers: observer.disconnect(); cuando termines. Evita circular references: previenen garbage collection en engines más antiguos. Usa document.createDocumentFragment() para batch DOM operations: fragments son más ligeros que full DOM nodes. Monitorea memory con PerformanceObserver: new PerformanceObserver((list) => {\\n for (const entry of list.getEntries()) {\\n if (entry.entryType === 'gc') {\\n console.log(GC: ${entry.duration}ms);\\n }\\n }\\n}).observe({ entryTypes: ['gc'] });. Usa navigator.deviceMemory para adaptar basado en RAM disponible: if (navigator.deviceMemory < 2) { /* low-end device */ }.

¿Cómo uso Speculation Rules API para prefetching?

La Speculation Rules API reemplaza los deprecated prerender y prefetch link relations. Añade rules via JSON en un script tag: <script type="speculationrules">\\n{\\n "prefetch": [\\n { "source": "list", "urls": ["/about", "/products"] }\\n ]\\n}\\n</script>. Usa document rules para dynamic prefetching: <script type="speculationrules">\\n{\\n "prefetch": [\\n {\\n "source": "document",\\n "where": { "selector_matches": "a.prefetch-link" },\\n "eagerness": "moderate"\\n }\\n ]\\n}\\n</script>. Usa eagerness para controlar cuándo prefetching happens: immediate (right away), moderate (on hover), conservative (on pointer down). Usa prerender para full page prerendering: <script type="speculationrules">\\n{\\n "prerender": [\\n { "source": "list", "urls": ["/next-page"] }\\n ]\\n}\\n</script>. Prerendered pages se cargan completamente en background, incluyendo JavaScript execution. Usa where con href_matches para pattern matching: "where": { "href_matches": "/products/*" }. Combina rules: prefetch algunas pages, prerender otras. Checkea support: if (HTMLScriptElement.supports('speculationrules')) { /* supported */ }. Usa no-vary-search hint: "no_vary_search": true le dice al browser reusar prefetched responses incluso si los URL parameters cambian. Monitorea speculation rules performance: new PerformanceObserver((list) => {\\n for (const entry of list.getEntries()) {\\n if (entry.name === 'Speculation-Rule') {\\n console.log(entry);\\n }\\n }\\n}).observe({ entryTypes: ['navigation'] });. Usa expectation field: "expectation": "successful" solo prefetcha si la response es likely successful (200 status). Usa relative_to para base URL: "relative_to": "/docs/" resuelve relative URLs. Limita prerendered pages: demasiadas consumen memory y CPU. Usa navigator.scheduling.isInputPending() para evitar prefetching durante user interaction. Usa Chrome flags para testear: chrome://flags/#enable-speculation-rules. Usa Speculation-Rules header: Speculation-Rules: "/rules.json" para cargar rules desde un file externo.

¿Cómo optimizo para dispositivos low-end?

Detecta dispositivos low-end: const isLowEnd = navigator.deviceMemory < 2 || navigator.hardwareConcurrency < 4;. Reduce JavaScript bundle: sirve una versión más ligera con menos features. Usa adaptive serving: if (isLowEnd) {\\n import('./lightweight-app.js');\\n} else {\\n import('./full-app.js');\\n}. Reduce image quality: <img srcset=\"image-300.webp 300w, image-600.webp 600w\" sizes=\"(max-width: 400px) 300px, 600px\">. Deshabilita animaciones: @media (prefers-reduced-data: reduce) {\\n * { animation: none !important; transition: none !important; }\\n}. Reduce font payload: usa system fonts en dispositivos low-end: font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;. Deshabilita features no esenciales: comments, recommendations, social sharing widgets. Usa Save-Data header: if (navigator.connection?.saveData) {\\n // Serve minimal version\\n}. Reduce DOM size: fewer elements significa menos memory y rendering más rápido. Usa CSS content-visibility: auto agresivamente. Evita heavy libraries: reemplaza moment.js con date-fns, reemplaza lodash con native methods. Usa requestIdleCallback para work no crítico: permite al browser priorizar rendering. Usa navigator.scheduling.isInputPending() para yield a user interactions. Sirve fewer, larger files: reduce HTTP overhead en conexiones lentas. Usa HTTP/2 o HTTP/3 para multiplexing. Cachea agresivamente con Service Workers: estrategias offline-first reducen network requests en visitas repetidas. Usa loading="lazy" para todo below-fold media. Defer todo JavaScript no crítico. Usa fetchpriority="low" para recursos below-fold. Monitorea long tasks: new PerformanceObserver((list) => {\\n for (const entry of list.getEntries()) {\\n if (entry.duration > 50) {\\n console.log(Long task: ${entry.duration}ms);\\n }\\n }\\n}).observe({ entryTypes: ['longtask'] });.

¿Cómo uso el Performance Panel en Chrome DevTools?

Abre DevTools con F12 o Ctrl+Shift+I, ve al tab “Performance”. Click “Record” (botón circular) e interactúa con tu page. Click “Stop” para analizar la grabación. Lee el flame chart: x-axis es tiempo, y-axis es call stack depth. Busca long yellow blocks (JavaScript), wide purple blocks (layout/reflow), y green blocks (paint). Usa “Bottom-Up” view para ver qué functions toman más total time. Usa “Call Tree” view para ver la hierarchical structure de function calls. Usa “Event Log” para ver individual events sorted by duration. Habilita “CPU: 4x slowdown” para simular mobile devices. Habilita “Network: Slow 3G” para testear en conexiones lentas. Usa “Screenshots” checkbox para ver visual frames durante la grabación. Usa “Memory” checkbox para trackear heap allocations. Usa performance.mark() y performance.measure() para añadir custom markers: performance.mark('start-render'); renderApp(); performance.mark('end-render'); performance.measure('render', 'start-render', 'end-render');. Usa el tab “Coverage” para encontrar JavaScript y CSS no usado: Ctrl+Shift+P > “Show Coverage”. Usa el tab “Rendering” para habilitar paint flashing y layout shift regions. Usa el panel “Insights” para automated performance suggestions. Usa performance.measureUserAgentSpecificMemory() para cross-origin memory measurement. Usa Lighthouse desde DevTools para un audit all-in-one: tab “Lighthouse” > “Generate report”.

¿Cómo optimizo HTTP headers para performance?

Usa Cache-Control: Cache-Control: public, max-age=31536000, immutable para static assets con hashed filenames. Usa stale-while-revalidate: Cache-Control: max-age=60, stale-while-revalidate=600 para dynamic content. Usa ETag para conditional requests: ETag: "abc123" — el browser envía If-None-Match: "abc123", el server responde con 304 Not Modified si no cambió. Usa Last-Modified: Last-Modified: Wed, 09 Jul 2026 12:00:00 GMT — el browser envía If-Modified-Since. Usa Content-Encoding: Content-Encoding: br para Brotli (20% más pequeño que gzip), Content-Encoding: gzip como fallback. Usa Content-Type con charset: Content-Type: text/html; charset=utf-8. Usa X-Content-Type-Options: nosniff para prevenir MIME sniffing. Usa Strict-Transport-Security: max-age=31536000; includeSubDomains para HTTPS enforcement. Usa X-Frame-Options: DENY para prevenir clickjacking. Usa Content-Security-Policy para restringir resource loading: Content-Security-Policy: default-src 'self'; script-src 'self' 'unsafe-inline'. Usa Cross-Origin-Opener-Policy: same-origin y Cross-Origin-Embedder-Policy: require-corp para cross-origin isolation (habilita SharedArrayBuffer). Usa Cross-Origin-Resource-Policy: same-origin para restringir resource access. Usa Permissions-Policy para deshabilitar APIs no usadas: Permissions-Policy: camera=(), microphone=(), geolocation=(). Usa Service-Worker-Allowed para expandir SW scope: Service-Worker-Allowed: /. Usa Date header para caching calculations. Usa Vary: Accept-Encoding para cachear versiones comprimidas y no comprimidas separadamente. Usa Vary: Cookie si el content varía por authentication state. Usa 103 Early Hints para preloading: Link: </styles.css>; rel=preload; as=style enviado antes del full response.

¿Cómo uso Resource Hints efectivamente?

Usa <link rel="preconnect"> para establecer early connections: <link rel="preconnect" href="https://cdn.example.com">. Usa <link rel="dns-prefetch"> para DNS-only resolution: <link rel="dns-prefetch" href="https://api.example.com">. Usa <link rel="preload"> para critical resources: <link rel="preload" href="/fonts/inter.woff2" as="font" type="font/woff2" crossorigin>. Usa <link rel="prefetch"> para likely next-page resources: <link rel="prefetch" href="/next-page.js">. Usa <link rel="prerender"> para full page prerendering (deprecated, usa Speculation Rules API en su lugar). Usa fetchpriority attribute: <img src="hero.webp" fetchpriority="high"> para above-fold images, <img src="below.webp" fetchpriority="low"> para below-fold. Usa importance en fetch: fetch('/api/critical', { importance: 'high' }). Usa requestIdleCallback para non-critical prefetches: requestIdleCallback(() => fetch('/next-page-data')). Usa navigator.connection.effectiveType para adaptar: if (navigator.connection.effectiveType === '4g') { prefetchNextPage(); }. Usa navigator.connection.saveData para respetar data-saving preferences. Usa Cross-Origin-Resource-Policy: cross-origin para cross-origin preloaded resources. Usa as attribute correctamente: as="script", as="style", as="font", as="image", as="fetch". Usa type attribute para module preloading: <link rel="modulepreload" href="/app.js">. Usa crossorigin para cross-origin fonts: <link rel="preload" href="https://cdn.example.com/font.woff2" as="font" crossorigin>.

Ver También


Última actualización: 2026-07-09