Inicio / Artículos / Tiempo de navegador, servidor o compilación: un mapa de decisiones para las arquitecturas frontend

Tiempo de navegador, servidor o compilación: un mapa de decisiones para las arquitecturas frontend

Vea cómo SSG, SSR, streaming, Server Components, BFFs, renderizado en el edge, monolitos modulares y microfrontends responden cada uno a una misma pregunta: ¿dónde debe realizarse el trabajo?

2588 palabras

Las discusiones sobre arquitectura, y en particular las entrevistas para puestos senior de frontend, rara vez se preocupan por lo que has construido. Lo que les importa es por qué lo construiste de esa manera, y “es porque el equipo ya lo tenía” no es una respuesta válida. La buena noticia es que casi todos los patrones de arquitectura frontend responden a la misma pregunta fundamental: ¿cuánto trabajo debe realizarse en el navegador, cuánto en el servidor y cuánto en el momento de la compilación? Una vez que entiendas que SSR, los componentes server-side, backends-for-frontends y micro-frontends son diferentes formas de trazar esa línea, elegir entre ellos y justificar la elección se vuelve mucho más sencillo.

Un comentario sobre las fuentes: las experiencias empresariales descritas a continuación provienen de artículos técnicos públicos y documentación oficial, y se atribuyen como tales.

Cómo cambió la línea entre cliente y servidor

La web de principios era sencilla. Los archivos HTML estáticos se cargaban casi al instante y había muy pocas cosas que pudieran fallar.

Luego, los frameworks MVC del lado del servidor como Django, Rails y ASP.NET comenzaron a generar HTML en cada solicitud. Las páginas podían mostrar ahora datos dinámicos, pero cada clic significaba una recarga completa de la página.

Las aplicaciones de una sola página desarrolladas con React, Vue o Angular hicieron que el equilibrio se inclinara por completo hacia el otro lado. El navegador asumió la gestión del enrutamiento, el estado, la validación e incluso, a veces, la autenticación. El resultado fue una interfaz de usuario que parece instantánea, pero solo después de haberse cargado. Esa condición es precisamente el problema: paquetes grandes de JavaScript, un tiempo lento para la primera renderización y una menor capacidad de descubrimiento. Aunque Google ejecuta JavaScript, la renderización puede retrasar el indexado en sitios grandes, y muchos otros rastreadores, incluidos los bots de vista previa de redes sociales y numerosos rastreadores basados en IA, nunca ejecutan scripts. El contenido que no pueden ver efectivamente no existe para ellos.

Visto desde la distancia, la historia de la arquitectura frontend es una serie de cambios entre clientes ligeros, donde el servidor realiza la mayor parte del trabajo, y clientes pesados, donde lo hace el navegador. Cada patrón que se describe en el resto de esta guía representa otra posición para ese límite.

Backend-for-Frontend: reconfigurando una API por cliente

En una configuración de Backend-for-Frontend (BFF), el equipo frontend ejecuta un servicio ligero propio delante de los servicios backend reales. Ese servicio tiene una única función: transformar los datos del backend en exactamente lo que necesita un único cliente.

Este patrón suele remontarse a SoundCloud. Alrededor de 2013, mientras dividían un sistema monolítico en microservicios, la empresa descubrió que los clientes para web, iOS y Android competían por una misma API compartida que no era adecuada para ninguno de ellos. Cada cliente recibió su propio backend dedicado y ligero. Phil Calçado, quien trabajó en esa migración, describió posteriormente su historia en detalle, y el artículo de Sam Newman lo convirtió en la descripción del patrón ampliamente citada que le dio su nombre. Netflix llegó a una solución similar de forma independiente, con capas adaptadoras específicas para cada dispositivo, ya que una aplicación para televisión y una aplicación para teléfono requieren cargas de trabajo muy diferentes.

Considere un caso concreto. Una aplicación móvil necesita el nombre del producto, su precio y una miniatura. La aplicación web, además, requiere reseñas, niveles de stock y artículos relacionados. Un único punto de extremo compartido envía demasiada información a las aplicaciones móviles o muy poca a las web, por lo que los equipos terminan añadiendo parámetros de consulta para compensar.

Un BFF resuelve este problema al proporcionar a cada tipo de aplicación una respuesta personalizada. El servicio Express que se muestra a continuación, el cual utiliza fetch incorporado en Node 18 y versiones posteriores, llama a la API del producto upstream y devuelve solo cuatro campos, renombrando title como name y reduciendo la lista de imágenes a una sola miniatura:

// bff.js — Node 18+, fetch is built in
import express from 'express';

const app = express();
const API = 'https://api.example.com';

app.get('/products/:id', async (req, res) => {
  const response = await fetch(`${API}/products/${req.params.id}`);
  const data = await response.json();

  res.json({
    id: data.id,
    name: data.title,
    price: data.price,
    thumbnail: data.images[0],
  });
});

app.listen(4000, () => console.log('BFF listening on :4000'));

El cliente solicita /products/123 y recibe exactamente la estructura que desea, sin campos adicionales ni viajes de ida y vuelta extra. En el código de producción también se debería verificar response.ok antes del análisis, y protegerse contra productos que no tengan imágenes, ya que data.images[0] asume que al menos existe una.

El costo de esto merece más atención de la que suele recibir. Un BFF es otro servicio que hay que desplegar, monitorear y mantener funcional. Si falla, la interfaz de usuario también fallará, incluso cuando el backend real está perfectamente bien. No se trata de infraestructura gratuita; es un punto adicional de fallo cuyo principal beneficio es una vida más cómoda para el equipo del frontend. Para conocer más sobre cómo crear uno dentro de una aplicación Next.js, consulte convertir los controladores de rutas de Next.js en una capa BFF deliberada.

Estrategias de renderizado: dónde se genera el HTML

El renderizado ha cambiado más que cualquier otro área en los últimos años, y en la práctica las categorías se difuminan más de lo que sugieren los diagramas.

Generación estática y regeneración incremental

Generación de sitios estáticos (SSG) renderiza cada página en el momento de la compilación y sirve archivos simples desde un CDN. No hay nada más rápido o económico para servir, pero el contenido permanece inalterado hasta el próximo despliegue.

Regeneración estática incremental (ISR) introduce una solución intermedia: una página puede reconstruirse en segundo plano después de un intervalo configurado. Se mantiene la mayor parte de la velocidad de los archivos estáticos sin necesidad de volver a desplegar cada vez que cambia el contenido.

Renderizado del lado del servidor y transmisión en flujo

El renderizado del lado del servidor (SSR) genera HTML para cada solicitud. En su forma clásica, el servidor envía la página completa y luego el navegador la hidrata: descarga el paquete de JavaScript y agrega manejadores de eventos y estado al marcado que ya está en la pantalla.

React 18 introdujo el SSR por streaming a través de renderToPipeableStream, el cual envía fragmentos de HTML tan pronto como cada parte del árbol está lista, en lugar de esperar al componente más lento. El streaming es la opción habitual para nuevas aplicaciones, aunque muchas aplicaciones en producción siguen funcionando sin problemas con el SSR clásico de una sola vez.

Componentes del servidor, islas y posibilidad de reanudación

React Server Components (RSC) y las arquitecturas de islas van aún más allá: solo las partes interactivas de una página envían JavaScript. El contenido estático permanece como HTML puro, sin nada que necesite ser hydratado. Las islas de Astro aplican esta idea fuera de React. Qwik va aún más lejos con la capacidad de reanudación, la cual evita en gran medida el proceso de hydratado al serializar el estado de la aplicación en el HTML, de modo que el cliente pueda continuar desde donde lo dejó el servidor.

El ejemplo a continuación muestra una página de Componente de Servidor en el Next.js App Router. A partir de Next.js 15, params es una Promise que debe ser esperada, por lo que el componente desestructura id solo después de ejecutar await params. La obtención de datos se realiza en el servidor, por lo que el nombre del producto y su precio llegan ya como HTML listo para renderizarse:

// app/products/[id]/page.tsx (Next.js 15+)
import { fetchProduct } from '@/lib/api';

export default async function ProductPage({
  params,
}: {
  params: Promise<{ id: string }>;
}) {
  const { id } = await params;
  const product = await fetchProduct(id); // runs on the server

  return (
    <main>
      <h1>{product.name}</h1>
      <p>${product.price}</p>
      <AddToCartButton productId={product.id} />
    </main>
  );
}

Solo AddToCartButton incluye JavaScript del lado del cliente. Para que esto sea posible, debe encontrarse en su propio archivo marcado con la directiva 'use client' e importarse en la página; el fragmento omite esa importación por brevedad. El resultado es un pequeño paquete que es interactivo donde la interacción es importante y estático en todo lo demás.

Renderizado en Edge y por qué la industria cambió parcialmente de rumbo

El renderizado en el edge es uno de los ejemplos más claros en los frontends modernos de una idea que se prueba públicamente y se modifica continuamente.

Entre aproximadamente 2021 y 2023, el argumento era convincente: ejecutar SSR en el edge, en plataformas como Cloudflare Workers o Vercel Edge Functions, en un centro de datos cercano a cada usuario. Menor distancia, páginas más rápidas. Vercel promocionó intensamente este enfoque.

Vercel posteriormente revirtió abiertamente su postura; su entonces vicepresidente de producto lo resumió como "este me engañó". La razón es ilustrativa. El procesamiento debe estar cerca del usuario, pero también cerca de los datos, y la mayoría de las bases de datos se encuentran en una sola región. Una función de borde en Tokio que realiza varios viajes ida y vuelta a una base de datos en Virginia suele ser más lenta que simplemente renderizarla en Virginia. Cuando Vercel midió esto en su propio producto, v0, el renderizado con Node.js puro superó al renderizado en entorno de borde. Vercel abandonó posteriormente las funciones de borde independientes y ahora recomienda el entorno de ejecución Node.js con el procesamiento ubicado en la misma región que los datos; consulte la documentación actual de la plataforma para conocer el estado exacto de cada entorno de ejecución.

Lo que queda es una idea más limitada: entregar de inmediato la estructura estática de una página desde el edge, y luego transmitir las partes dinámicas desde servidores ubicados cerca de los datos. Eso es, en esencia, lo que hace Partial Prerendering; la explicación sobre el preprocesamiento parcial y el procesamiento concurrente detalla los mecanismos.

Cloudflare Workers sigue siendo una verdadera plataforma SSR de edge y funciona bien cuando los datos están distribuidos globalmente. La lección clave es que la localidad de los datos suele ser más importante que la localidad del usuario. Entender por qué la industria cambió de opinión es más valioso que conocer solo el término de moda.

El monolito frontend modular

Cuando una aplicación de página única crece más allá de un pequeño número de equipos, un repositorio plano se vuelve arriesgado. Todos editan los mismos componentes compartidos, y nadie está seguro de quién es el responsable de qué.

Un monolito modular separa la base de código en dos capas, manteniendo una sola lista para desplegar:

  • Una capa de plataforma, gestionada por un equipo de plataforma, que contiene el sistema de diseño, los ganchos compartidos, el registro de actividades y otras infraestructuras similares.
  • Una capa de dominio formada por carpetas de funcionalidades como user/ o payments/, cada una gestionada por un equipo de funcionalidades.

La motivación es similar a la arquitectura limpia o hexagonal, pero sin casi toda su formalidad. La arquitectura limpia completa suele ser excesiva en el frontend; un botón y una llamada de fetch no necesitan tres capas de abstracción entre ellos. Lo importante es tener una responsabilidad clara y límites estrictos, como por ejemplo reglas de lint que impidan que un dominio importe los componentes internos de otro.

Micro-frontends: independencia a un costo

Una arquitectura de micro-frontends trata cada dominio como una mini-aplicación independiente que se puede desplegar por separado, generalmente cargada en tiempo de ejecución por una aplicación principal a través de un mecanismo como Webpack Module Federation.

Lo que se obtiene es una autonomía real: los equipos publican según sus propios horarios y, si realmente es necesario, incluso pueden utilizar frameworks diferentes. Zalando, IKEA y DAZN han descrito cómo implementan esto a gran escala, siempre con grandes organizaciones de ingeniería e inversiones significativas en herramientas compartidas. micro-frontends.org sigue siendo la referencia estándar para conocer todos los detalles del caso.

El modo de fallo que realmente causa problemas

El problema que se repite en los informes reales de incidentes no es la mezcla de frameworks. Se trata de la deriva en las dependencias compartidas. Una aplicación remota actualiza una biblioteca compartida mientras que otra no lo hace, y de repente hay dos copias de React ejecutándose en la misma página y compitiendo por el mismo DOM. Module Federation puede declarar singulares compartidos y rangos de versión para evitar esto, pero solo si los equipos acuerdan y aplican esas restricciones. Ese problema específico de coordinación es lo que agota a los equipos, mucho más que la vaga “complejidad” que suele mencionarse.

También existe un ejemplo de advertencia en la dirección opuesta. Se dice que Spotify experimentó con un enfoque de microfrontend basado en iframe en su cliente de escritorio hace años y posteriormente lo consolidó en una arquitectura unificada, en parte porque las conexiones entre las distintas partes costaban más que la independencia que esto ofrecía. Incluso a gran escala, este patrón no garantiza necesariamente un éxito.

Deje que el tamaño del equipo impulse la decisión

La pregunta que rara vez aparece en los diagramas de arquitectura es cuántos ingenieros se tienen realmente. Los rangos siguientes son heurísticas extraídas de la forma en que los equipos suelen describir posteriormente dicha decisión, y no reglas estrictas:

  • un monolito modular gana casi siempre. No hay suficientes personas como para justificar pipelines de despliegue separados.
  • Aproximadamente de 15 a 50 ingenieros con unos límites de dominio claros: Los BFF combinados con un monolito modular bien organizado suelen ser suficientes. Los microfrontends probablemente siguen siendo prematuros.
  • Más de unos 50 ingenieros, con equipos que realmente se interrumpen mutuamente en los lanzamientos: Los microfrontends comienzan a ser útiles, no porque la aplicación haya crecido sino porque la organización sí lo ha hecho.
  • Explicar de manera convincente una elección arquitectónica

    Los puestos de alto nivel esperan una decisión, no un catálogo de opciones. Una respuesta sólida suele tener cuatro partes:

    1. Qué se ejecuta. Por ejemplo: las páginas de marketing se generan estáticamente, los paneles utilizan SSR y un BFF se encuentra delante de la aplicación móvil.
  • Por qué. La generación estática permite una carga muy rápida del contenido que rara vez cambia; el SSR permite que los datos personalizados, como el nombre del usuario, aparezcan sin mostrar contenido incorrecto.
  • El costo que decidieron asumir. El BFF introduce un paso adicional, pero permite al equipo frontend tener el control total sobre su contrato de datos, lo que justifica su uso.
  • Lo que rechazaron y por qué. Se evaluaron los microfrontends, pero la sobrecarga de coordinación no se justificaba con el tamaño actual del equipo.
  • El último punto es el de mayor importancia. Explicar qué decidieron no elegir intencionalmente es lo que diferencia simplemente describir un diagrama de tomar una decisión real. El cambio en el proceso de renderizado en los bordes es un ejemplo claro: incluso la plataforma que promovió este enfoque cambió de rumbo cuando los resultados de las mediciones no coincidieron.

    Preguntas frecuentes

    ¿En qué difieren SSR y SSG?

    Dado que SSR se renderiza en cada solicitud, puede incluir datos en tiempo real o específicos para cada usuario. SSG genera el HTML una sola vez durante el proceso de compilación y sirve archivos estáticos, lo que es más rápido y económico, pero solo está actualizado hasta la última implementación. ISR se sitúa entre ellos al regenerar páginas individuales según un temporizador.

    ¿Vale la pena usar un BFF si solo hay una interfaz frontal?

    Por lo general, no. Un BFF cobra sentido cuando varios clientes, como dispositivos móviles, la web y una API de un socio, necesitan cargas de trabajo significativamente diferentes desde un mismo backend. Con una sola interfaz frontal, generalmente solo añade un salto de red y otro servicio que operar.

    ¿Está muerto el renderizado en el edge?

    No, pero el valor por defecto ha cambiado. Servir shells estáticos desde el edge sigue siendo valioso, y las plataformas de edge como Cloudflare Workers destacan cuando los datos están distribuidos globalmente. Para aplicaciones típicas respaldadas por una base de datos en una sola región, la regla general ahora es colocar el procesamiento cerca de los datos y no cerca del usuario.

    ¿Cuándo debería un equipo pasar a los microfrontends?

    Cuando la coordinación de lanzamientos entre equipos se convierte en el verdadero cuello de botella, y no antes. Una aplicación compleja gestionada por un único equipo obtiene los mismos beneficios organizativos de un monolito modular con una fracción del esfuerzo adicional.

    Puntos clave

    • Cada patrón aquí es una respuesta a una pregunta: ¿cuánto trabajo corresponde al navegador, al servidor y a la fase de compilación?
  • La posición adecuada depende mucho más del tamaño del equipo, de dónde se encuentran los datos y de cuán problemáticos son los despliegues, que del hecho de qué patrón suene más impresionante.
  • Los BFF, los microfrontends y el renderizado en el edge sacrifican costos operativos a cambio de un beneficio específico; indique claramente ese compromiso.
  • Tenga listo el razonamiento detrás de las opciones rechazadas. Es la evidencia más convincente de que la elección fue deliberada.