Guía de Desarrollo Frontend
Descripción General
Esta guía cubre el desarrollo frontend moderno con React, Vue, Angular y Svelte, incluyendo arquitectura de componentes, optimización del rendimiento, accesibilidad y pruebas. Úsala al construir aplicaciones web, librerías de componentes u optimizar el rendimiento frontend.
Reglas de Decisión de Framework y Diseño
- Al elegir un framework, adáptalo a la experiencia del equipo y las restricciones del proyecto; por defecto usa React para necesidades ecosistémicas amplias, Vue para mejora progresiva en páginas existentes, Svelte para aplicaciones críticas en tamaño de paquete, y Angular cuando el proyecto requiera un framework completo y opinado con DI y enrutamiento integrados.
- Al implementar un diseño, usa CSS Grid para diseños de página bidimensionales y Flexbox para alineación de componentes unidimensional; evita el posicionamiento absoluto para propósitos de diseño porque rompe el reflujo responsivo.
- Al construir una librería de componentes, expón cada componente como una exportación nombrada con una interfaz de props TypeScript, una historia de Storybook y una prueba unitaria; los componentes sin los tres no se fusionan.
- Al integrar con APIs backend, centraliza la lógica de fetch en una capa de cliente API tipado (por ejemplo, un módulo
api.tsusandofetchoaxioscon interceptores) para que los encabezados de autenticación, transformaciones de error y reintentos se manejen en un solo lugar.
Reglas de Decisión de Rendimiento
- Cuando el Largest Contentful Paint de una página exceda 2.5 segundos en Lighthouse CI, trátalo como un error bloqueante: perfila con la pestaña Rendimiento de Chrome DevTools y corrige el cuello de botella más grande antes de fusionar.
- Al agregar animaciones, usa propiedades CSS
transformyopacity(solo compositor) en lugar dewidth,heightotop/leftpara evitar desencadenar recálculos de diseño que causen tirones. - Cuando la aplicación necesite soporte offline, registra un service worker con una estrategia cache-first para activos estáticos y una estrategia network-first para solicitudes API, cayendo en respuestas cacheadas cuando esté offline.
- Cuando el paquete JS inicial exceda el presupuesto (por ejemplo, 200 KB comprimido con gzip), agrega división de código basada en rutas con
React.lazy()oimport()dinámico y difiere scripts no críticos por debajo del pliegue. - Al soportar navegadores antiguos, define una configuración browserslist y deja que la herramienta de construcción (Vite, Webpack) auto-polyfill; no agregues polyfills manualmente ni verificaciones de características a menos que la cobertura de browserslist sea insuficiente.
- Al agregar imágenes, usa
<picture>con fuentes WebP/AVIF y atributos explícitoswidth/heightpara prevenir cambios de diseño; para imágenes debajo del pliegue, agregaloading="lazy". - Cuando una ruta no sea necesaria en la carga inicial de la página, envuélvela en
React.lazy()(o equivalente del framework) con un fallback<Suspense>para que el paquete principal excluya el código de esa ruta. - Al servir activos estáticos, configura el CDN o servidor para establecer
Cache-Control: public, max-age=31536000, immutableen nombres de archivo con hash de contenido yno-cacheenindex.html.
Reglas de Decisión de Accesibilidad
- Al construir cualquier componente interactivo, agrega atributos ARIA, manejadores de teclado (
Enter,Space,Escapesegún corresponda) y prueba con axe-core antes de marcar la tarea como completa. - Al construir formularios, asocia cada
<input>con un<label>mediantehtmlFor/id, proporciona mensajes de error visibles vinculados conaria-describedby, y asegúrate de que el formulario sea completamente operable con solo navegación por teclado. - Al usar color para transmitir significado (por ejemplo, estados de error, insignias de estado), incluye siempre un indicador secundario (icono, texto, patrón) para que los usuarios daltónicos puedan distinguir los estados.
- Al agregar un modal o menú desplegable, atrapa el foco dentro del elemento mientras esté abierto y devuelve el foco al elemento desencadenante al cerrar; prueba tabulando todo el flujo sin ratón.
- Cuando una solicitud de extracción agregue un nuevo componente interactivo, la PR debe incluir una prueba de integración con axe-core que afirme cero violaciones WCAG 2.1 AA antes de que pueda fusionarse.
Reglas de Decisión de Depuración CSS
Cuando un diseño se rompa, diagnostica por síntoma:
- El hijo flex se desborda de su contenedor: Agrega
min-width: 0(fila) omin-height: 0(columna) al hijo flex. Los elementos flex por defecto tienenmin-width: auto, lo que impide que se encojan por debajo del tamaño del contenido. - Los elementos de grid ignoran el ancho de columna: Usa
minmax(0, 1fr)en lugar de1fr.1frsimple significaminmax(auto, 1fr), lo que permite que el contenido empuje la columna más ancha de lo previsto. position: stickyno se adhiere: Revisa cada ancestro en busca deoverflow: hidden,overflow: autoooverflow: scroll. Cualquiera de estos crea un nuevo contexto de desplazamiento que contiene el elemento sticky. También verifica que el elemento tenga un valortop/bottomestablecido.- Elemento centrado con
margin: autono se centra: Verifica que el elemento tenga unwidthexplícito (bloque) o que el padre tengadisplay: flex(hijo flex).margin: autono hace nada en elementos de bloque de ancho completo. z-indexno funciona: El elemento necesitaposition: relative/absolute/fixed/stickypara crear un contexto de apilamiento. También verifica si un ancestro crea un contexto de apilamiento (a través detransform,opacity < 1,filterowill-change) que limite el alcance del z-index.- Espacio entre elementos inline-block/inline: El espacio en blanco en HTML crea espacios. Solución: usa
display: flexen el padre (preferido), o establecefont-size: 0en el padre y restáuralo en los hijos. - Espacio de 1px entre elementos adyacentes: Renderizado de subpíxeles. Solución: usa
outlineen lugar deborderpara depurar, o establecebackgrounden el padre para enmascarar el espacio. - Truncamiento de texto con puntos suspensivos no funciona: Requiere los tres:
overflow: hidden,white-space: nowrap,text-overflow: ellipsis. Para truncamiento de varias líneas, usa-webkit-line-clampcondisplay: -webkit-boxy-webkit-box-orient: vertical. - Las consultas de contenedor no se aplican: Verifica que el ancestro tenga
container-type: inline-size(osize). El elemento mismo no puede ser su propio contenedor: la consulta debe referenciar a un padre.
Reglas de Decisión de Herramientas de Construcción
- Vite (por defecto): Úsalo para todos los proyectos nuevos. Servidor de desarrollo instantáneo mediante ESM nativo, HMR rápido, construcciones de producción basadas en Rollup. Si el proyecto usa dependencias solo CommonJS que fallan con Vite, agrega
optimizeDeps.includepara esos paquetes; no cambies a Webpack. - Webpack: Úsalo solo para proyectos existentes que ya lo usen, o cuando un plugin específico de Webpack no tenga equivalente en Vite (raro). Migra a Vite cuando surja la oportunidad (ver Reglas de Decisión de Migración).
- Turbopack: Úsalo solo dentro de Next.js 14+ (
next dev --turbo). Aún no utilizable de forma independiente. Proporciona construcciones de desarrollo más rápidas que Webpack para proyectos Next.js. Si las construcciones ya son rápidas (<5s), no te molestes en cambiar. - esbuild: Úsalo para empaquetado de librerías, scripts o herramientas CLI, no para aplicaciones completas. No maneja puntos de entrada HTML, ni división de código por ruta. Usa
tsup(envoltorio de esbuild) para publicar paquetes npm. - SWC vs Babel: Usa SWC (a través del plugin de Vite o por defecto en Next.js). SWC es 20-70 veces más rápido para transformaciones. Usa Babel solo si un proyecto depende de un plugin solo de Babel sin equivalente en SWC (cada vez más raro).
- Herramientas de monorepo: <5 paquetes = workspaces de npm/pnpm, sin herramienta adicional. 5-20 paquetes = Turborepo para caché de tareas. >20 paquetes o poliglotas = Nx. Nunca agregues herramientas de monorepo a un proyecto de un solo paquete.
Reglas de Decisión de SSR y Componentes del Servidor
Cuándo usar SSR vs CSR vs SSG
- SSG (Generación de Sitios Estáticos): Úsalo para contenido que cambia menos de una vez por hora (páginas de marketing, documentación, publicaciones de blog). Construye en tiempo de despliegue. TTFB más rápido posible.
- SSR (Renderizado del Lado del Servidor): Úsalo para contenido personalizado (paneles de control, perfiles de usuario), páginas críticas para SEO con datos dinámicos, o páginas donde los datos desactualizados son inaceptables. Agrega latencia del servidor a cada solicitud; almacena en caché agresivamente con
Cache-Controlo CDN. - CSR (Renderizado del Lado del Cliente): Úsalo para páginas solo autenticadas (paneles de administración, herramientas internas) donde el SEO no importa y la interactividad es la prioridad. Más sencillo de construir y desplegar (alojamiento estático).
- ISR (Regeneración Estática Incremental): Úsalo para páginas que son mayormente estáticas pero necesitan actualizaciones periódicas (listados de productos, páginas de precios). Establece el intervalo
revalidatesegún cuán desactualizados puedan estar los datos: 60s para precios, 3600s para publicaciones de blog.
Reglas de Decisión de Componentes del Servidor React (RSC)
- Por defecto a Componentes del Servidor: Cada componente es un Componente del Servidor a menos que necesite interactividad. Si un componente tiene
onClick,useState,useEffecto APIs del navegador, debe ser un Componente del Cliente ('use client'). - Obtención de datos: Obtén datos en Componentes del Servidor con
async/awaitdirectamente; no usesuseEffect, TanStack Query ni estados de carga necesarios. Los datos se resuelven antes de que se envíe el HTML. - Autenticación en Componentes del Servidor: Lee la sesión/cookie en un Componente del Servidor y pasa el objeto de usuario como prop a los Componentes del Cliente. Nunca leas cookies o sesiones en Componentes del Cliente; se ejecutan en el cliente.
- Cuándo usar Acciones del Servidor vs rutas API: Usa Acciones del Servidor (
'use server') para envíos de formularios y mutaciones que se llaman desde la UI. Usa rutas API (/api/*) para webhooks, integraciones de terceros o endpoints llamados por servicios externos. - Compartir estado entre Componentes del Servidor y del Cliente: Pasa datos serializables como props de Servidor a Cliente. Si necesitas estado del lado del cliente derivado de datos del servidor, inicializa
useStatecon la prop del servidor y gestiona las actualizaciones en el cliente. - Conjuntos de datos grandes: Transmite con límites
<Suspense>. Envuelve el Componente del Servidor lento en<Suspense fallback={<Skeleton />}>. El shell se renderiza inmediatamente; el componente lento se transmite cuando está listo. Coloca los límites de Suspense en secciones UI significativas (barra lateral, contenido principal, comentarios), no alrededor de cada componente.
Árbol de Decisión de Errores de Hidratación
- Error: "Text content does not match": Un valor difiere entre el renderizado del servidor y del cliente. Causas comunes:
Date.now(),Math.random()onew Date().toLocaleString()en el renderizado → muévelo auseEffect+useState.- Renderizado condicional
typeof window !== 'undefined'→ usauseEffectpara establecer una banderahasMounted. - Extensiones del navegador inyectando nodos DOM → no es tu error, pero prueba en modo incógnito para confirmar.
- Error: "Hydration failed because the server rendered HTML didn't match": Una diferencia estructural (elementos diferentes). Causas comunes:
<p>anidado dentro de<p>, o<div>dentro de<p>→ corrige el anidamiento, la especificación HTML no lo permite.- Un componente de terceros se renderiza diferente en el servidor vs el cliente → envuélvelo en
dynamic(() => import(...), { ssr: false }). - Etiquetas de cierre faltantes o etiquetas de autocierre donde HTML requiere cierre explícito.
Flujo de Trabajo de Depuración de Rendimiento
Cuando una página sea lenta, sigue esta secuencia; no omitas pasos:
Paso 1: Identifica el tipo de cuello de botella
- Ejecuta Lighthouse. Verifica qué métrica está fallando:
- LCP >2.5s: El elemento visible más grande carga demasiado lento. Ve al Paso 2.
- FID/INP >200ms: La interacción del usuario está bloqueada por JavaScript. Ve al Paso 3.
- CLS >0.1: Cambios de diseño después de la pintura inicial. Ve al Paso 4.
Paso 2: Corrige LCP
- Verifica cuál es el elemento LCP (Lighthouse lo muestra). Generalmente: imagen de héroe, texto de encabezado o video.
- Si es imagen: Agrega
priority(Next.js) ofetchpriority="high". Agregawidth/heightexplícitos. Sirve en WebP/AVIF. Si se sirve desde un CDN, verifica los encabezados de caché. - Si es texto: Verifica si hay fuentes que bloqueen el renderizado. Usa
font-display: swapofont-display: optional. Precarga el archivo de fuente crítico con<link rel="preload" as="font" crossorigin>. - Si está bloqueado por JS: El paquete es demasiado grande. Revisa el Paso 3. El elemento LCP no puede renderizarse hasta que el JS que lo crea se cargue y ejecute.
- Verifica el tiempo de respuesta del servidor (TTFB). Si TTFB >600ms, el cuello de botella es backend o CDN, no frontend.
Paso 3: Corrige INP (Interacción con la Siguiente Pintura)
- Abre Chrome DevTools → Rendimiento → graba una interacción de clic/escritura.
- Encuentra la Tarea Larga (barra amarilla >50ms). Haz clic en ella para ver la pila de llamadas.
- Si la tarea larga es tu código: Divide el trabajo en fragmentos más pequeños con
requestIdleCallback,scheduler.yield()(Chrome 115+) osetTimeout(fn, 0)para ceder al navegador entre fotogramas. - Si la tarea larga es un re-renderizado de React: Abre el Perfilador de React DevTools. Encuentra el componente que se re-renderiza. Correcciones comunes:
React.memo(hijos costosos),useMemo(valores derivados) o mueve el estado más cerca de donde se usa. Si >500 componentes se re-renderizan en un solo cambio de estado, el estado está demasiado arriba en el árbol. - Si la tarea larga es un script de terceros (analíticas, anuncios): Difiere con el atributo
asyncodefer, o carga después derequestIdleCallback.
Paso 4: Corrige CLS
- Verifica qué elemento se desplaza (Lighthouse lo muestra, o usa DevTools → "Regiones de Cambio de Diseño").
- Imagen/video sin dimensiones: Agrega atributos
widthyheight(oaspect-ratioCSS). El navegador reserva espacio antes de que el activo cargue. - Intercambio de fuentes: La fuente de respaldo tiene métricas diferentes a la fuente web. Usa
size-adjusten@font-facepara igualar métricas, o usafont-display: optionalpara eliminar el intercambio por completo. - Contenido dinámico inyectado sobre el pliegue: Anuncios, pancartas, avisos de cookies. Reserva espacio con
min-heighten el contenedor. Si la altura del contenido varía, usacontain: layoutpara evitar que los cambios se propaguen. - CSS de carga tardía: Si una hoja de estilo carga después de la primera pintura y cambia el diseño visible, alinea el CSS crítico o precarga la hoja de estilo.
Reglas de Decisión de Calidad de Código
- Al escribir pruebas, exige que cada componente tenga al menos una prueba unitaria que cubra su ruta de renderizado principal y una prueba de interacción (clic, teclado); aplícalo mediante un umbral de cobertura CI de 80% de cobertura de línea como mínimo.
- Al iniciar un proyecto, habilita
strict: trueentsconfig.jsondesde el día uno; retroadaptar el modo estricto más tarde es exponencialmente más difícil a medida que crece la base de código. - Cuando una llamada API u operación asíncrona falle, muestra un mensaje de error visible para el usuario con una acción de reintento; nunca tragues errores en silencio ni muestres texto de excepción sin procesar.