Mejores prácticas de rendimiento
Resumen condensado de las 25 mejores prácticas más importantes extraídas de todas las páginas de esta sección.
- Analiza con @next/bundle-analyzer: Haz que el tamaño del bundle sea visible ejecutando el analizador en CI y en compilaciones locales con
ANALYZE=true; recuerda que el mapa de árbol muestra bytes sin comprimir, así que verifica el tamaño de transferencia gzip/brotli en el panel de Red antes de celebrar victorias. - Prohibe re-exportaciones de Barrel Files: Los archivos
index.tsconexport *silenciosamente anulan tree-shaking incluso en módulos ES a menos que establescas"sideEffects": falseenpackage.json; prefiere importaciones profundas o marca paquetes sin efectos secundarios para que el bundler pueda podar código no utilizado. - Mide CWV en Producción: Las Core Web Vitals en modo desarrollo son dos a cinco veces peor que en producción debido a código sin minificar, advertencias de React en desarrollo y source maps — siempre compara los objetivos (
LCP < 2.5s,INP < 200ms,CLS < 0.1) con una compilación de producción. - CLS se acumula a lo largo de la sesión: CLS es acumulativo en toda la sesión del usuario, así que modales, tostadas y banners de consentimiento que aparecen 30 segundos después aún cuentan — reserva espacio para UI diferida y prueba cambios de layout después del primer renderizado, no solo durante la carga.
- Paraleliza con Suspense de hermanos: Cada límite de
Suspensecon su propio Server Component asincrónico obtiene datos en paralelo automáticamente, así que dividir hermanos en límites separados te da paralelismo sin escribirPromise.all— un límite de nivel superior lo colapsa de nuevo en una cascada. - Elimina N+1 con include/select de Prisma: Iterar una lista con
.map(async row => prisma.x.findUnique(...))emite una consulta por fila; colapsa a un único JOIN medianteinclude/selecty prefierePromise.allSettledcuando un rechazo parcial no debería descartar todos los demás resultados. - Proporciona siempre
sizescon next/image: Sinsizes, el navegador asume100vwy descarga la variante srcset más grande (a menudo 3840px) para una miniatura; pasa una cadena consciente de puntos de quiebre que coincida con tu layout y añadeprioritysolo a la imagen de LCP para que el ancho de banda se gaste donde importa. - Auto-aloja fuentes con next/font: Reemplaza las etiquetas
<link>de Google Fonts connext/font/google(onext/font/local) para que las fuentes se envíen desde el mismo origen con encabezados de caché inmutable y métricas de fallback generadas automáticamente que eliminen FOUT y CLS; las fuentes variables ahorran alrededor de 100 KB en comparación con cargar múltiples archivos de peso. - Memoiza después de perfilar: Recurre a
useMemo,useCallbackyReact.memosolo después de que el profiler muestre un costo real; memoizar prematuramente componentes económicos añade sobrecarga de comparación que frecuentemente supera el trabajo que ahorra. - memo es inútil con children: Un componente que recibe
children(u un prop de objeto/función en línea) reconstruye ese valor en cada renderizado del padre, así queReact.memonunca ataja — ya sea eleva los children, usa el patrón de wrapper de estado children, o acepta que memo no ayudará allí. - Limpia cada asignación de efecto: Cualquier
useEffectque inicie una búsqueda, añada un listener, establezca un temporizador, abra un WebSocket o inicialice una biblioteca de gráficos debe devolver una limpieza; el double-mount del Modo Estricto expone la limpieza que falta en lugar de causarla, e intervals/listeners filtrados retienen todo el estado del componente a través del cierre. - Cancela búsquedas con AbortController: Conecta cada búsqueda iniciada por efecto a un
AbortControllery llama aabort()en la limpieza, así que cambios de dependencia o desmontajes no dejan respuestas obsoletas sobrescribiendo estado más nuevo o bloqueando GC de grandes payloads JSON. - Conoce las cuatro capas de caché: Next.js tiene Request Memoization (por renderizado), Data Cache (cross-request fetch), Full Route Cache (HTML prerenderizado) y Router Cache (en memoria del cliente); cualquier llamada
cookies()/headers()ocache: "no-store"desactiva Full Route Cache para toda la ruta, así que aísla ese código en su propio hijo Suspense. - Cachea llamadas de BD con unstable_cache: Data Cache solo envuelve la API Fetch, así que llamadas de Prisma/Drizzle lo evitan — envuélvelas en
unstable_cache(fn, keyParts, { tags, revalidate })con partes clave únicas y descriptivas, y recuerda querevalidatePath("/x")no toca rutas secundarias a menos que pases"layout". - Ejecuta Lighthouse con la mediana: Las ejecuciones individuales de Lighthouse varían 10-15 puntos en infra CI compartida, así que ejecuta al menos tres veces (
numberOfRuns: 3) y compara la mediana; establece umbrales de CI más bajos que tus números de producción locales para evitar fallos inestables. - Perfila solo compilaciones de producción: El Profiler de React DevTools en desarrollo es 2-5 veces más lento que en producción por advertencias de solo desarrollo y rutas de reconciliador; compila con
NODE_ENV=production, usareact-dom/profiling, cierra la pestaña Components durante la grabación, y comparaactualDurationvsbaseDurationpara ver si la memoización realmente está pagando. - Coloca estado y usa el patrón de children: Mueve el estado al componente más pequeño que realmente lo usa y pasa el subárbol estático como
childrena un wrapper que posea el estado; los children se crean en el ámbito del padre, así que el wrapper con estado puede re-renderizarse sin re-renderizar el árbol estático — no se necesitamemo. - Divide contextos por preocupación: Un único contexto que transporta tanto estado como dispatch re-renderiza cada consumidor en cualquier cambio; divide en contextos separados (estado vs acciones) o usa Zustand/
useSyncExternalStorepara que los suscriptores de grano fino solo se re-rendericen cuando su slice específico cambie. - Permite que el compilador de React memoice: Habilitar
experimental.reactCompiler: true(másbabel-plugin-react-compiler) inserta memoización más granular queuseMemo/useCallback/memoescritos a mano — adopta el compilador antes de arrancar memoización manual para que nunca dejes una brecha donde nada esté memoizado. - Mantén el renderizado puro para el compilador: El compilador asume que el renderizado es determinístico y libre de efectos secundarios, así que logging,
Date.now()/Math.random()durante el renderizado o lecturas desde globales mutables o dejan de dispararse o cachean valores obsoletos — usauseSyncExternalStorepara fuentes externas mutables. - Envía "use client" a las hojas: Añadir
"use client"en un layout contamina todo el subárbol y añade 100+ KB de JS de hidratación; mantén la directiva en la hoja interactiva más pequeña y pasa Server Components a través dechildrenen wrappers de Client Component como<ThemeProvider>. - Pasa props serializables a través del límite: Dates, Maps, Sets, instancias de clase y callbacks no pueden cruzar el límite Server→Client limpiamente — convierte dates a cadenas ISO y reemplaza callbacks con Server Actions; tampoco nunca importes
fs,prismau otros módulos solo de servidor dentro de un archivo"use client". - Selecciona con useShallow en Zustand:
useStore(s => s)se suscribe a la tienda completa, yuseStore(s => ({ a: s.a, b: s.b }))crea un objeto fresco en cada actualización causando re-renderizados espurios; envuelve enuseShallowdesdezustand/react/shallowpara comparar propiedades en lugar de identidad. - Trasmite con Suspense granular: Coloca un límite de Suspense por sección lógica de UI con un skeleton que coincida con las dimensiones del contenido final (para mantener CLS en cero), y empareja cada límite con un límite de error para que una sección fallida no bloquee la página completa — evita tantos límites pequeños que la UI "aparezca" incoherentemente.
- Cumple presupuestos de bundle en CI: Añade presupuestos de tamaño de bundle a la compilación (Webpack
performance.hints: "error", size-limit o la salidaanalyzede Next.js comparada con main) para que las regresiones causen fallo en la compilación en lugar de enviarse silenciosamente; empareja eso con una verificación de Lighthouse CI para que tanto bytes como métricas percibidas por el usuario cierren fusiones.