Caso de estudio: arquitectar una app grande de punta a punta
Poner todo junto: el plano completo de una aplicación grande con contenido público, catálogo, panel privado y API. Cómo se reparten colecciones, actions, auth, i18n y modos de render entre cuatro zonas, cómo el middleware compone los flujos transversales, y cómo el adapter de edge cierra el círculo con un deploy coherente donde cada pieza del track ocupa su lugar.
Ha llegado el momento de poner todo junto. No una pieza aislada, sino el plano entero de una aplicación grande de principio a fin: un producto con contenido público, un catálogo, un panel privado y una API, en varios idiomas y desplegado en el edge. Cada herramienta que recorriste en el track —colecciones, actions, sesiones, middleware, adapters, i18n— reaparece aquí, pero ya no como tema suelto sino como una pieza que ocupa su lugar exacto dentro de un sistema. Arquitectar es precisamente eso: dar a cada verdad el sitio donde pertenece.
- Trazar el plano completo de una app grande repartida en cuatro zonas.
- Repartir colecciones, actions, auth e i18n según la naturaleza de cada zona.
- Componer los flujos transversales con una secuencia de middleware.
- Cerrar el círculo con un adapter de edge y un deploy coherente.
El plano: cuatro zonas, un proyecto
Una app grande no es homogénea: es un archipiélago de zonas con naturalezas distintas conviviendo en un solo build. Cuatro bastan para casi todo.
Zona pública
Marketing, blog y documentación. Content collections tipadas, estático puro, cero JS, SEO máximo e i18n por routing. El corazón content-first de Astro.
Catálogo
Fichas y listados. Cáscara estática cacheable con server islands para el precio y el stock: parece dinámico, se sirve como estático con huecos frescos.
Panel privado
La aplicación. SSR tras un middleware de auth, sesiones para el estado, actions para las mutaciones e islas para los widgets interactivos.
API
Endpoints para webhooks entrantes, integraciones y la app móvil. Verbos HTTP y objetos Request y Response estándar, sin una página de por medio.
El reparto en el sistema de ficheros hace visible esta anatomía, y cada carpeta declara su modo de render por defecto:
src/
content/ # colecciones: blog, docs (Content Layer, tipadas con Zod)
pages/
[locale]/ # zona publica estatica, i18n por routing
tienda/ # catalogo: estatico mas server islands de precio y stock
panel/ # app SSR privada (prerender false)
api/ # endpoints: webhooks, movil, integraciones
actions/ # mutaciones del panel, validadas y con accept form
middleware.ts # la secuencia transversal: log, seguridad, auth, i18n
Por qué cuatro zonas y no una
La tentación del principiante es tratar todo el sitio con un solo modo de render y una sola estrategia de datos, y esa uniformidad es justo lo que arruina un producto grande: o bien vuelve dinámico el contenido que debía ser estático —y pagas cómputo por cada artículo—, o bien intenta hornear lo que dependía del usuario —y no puede—. Separar en zonas no es burocracia: es reconocer que un blog y un panel no comparten naturaleza y no deben compartir régimen. Cada zona tiene su modo de render, su fuente de datos y su presupuesto de JavaScript, y la coherencia del conjunto nace precisamente de respetar esas diferencias en lugar de aplanarlas.
Los flujos transversales: auth, i18n y datos
Lo que es cierto para muchas rutas no vive en ninguna página: vive en el middleware, compuesto como una secuencia ordenada donde el orden codifica las dependencias. El registro envuelve todo, la seguridad blinda cada respuesta, la autenticación resuelve la sesión en locals antes de que ninguna ruta privada la necesite, y el idioma se lee de la URL una vez para todo el árbol.
// src/middleware.ts
import { sequence } from 'astro:middleware';
import { logging, seguridad, auth, i18n } from './middleware/index';
// El orden es la arquitectura: registrar, blindar, autenticar, traducir
export const onRequest = sequence(logging, seguridad, auth, i18n);
Los datos siguen la misma lógica de reparto que el render: el contenido editorial entra por el Content Layer —colecciones tipadas, cacheadas incrementalmente—, mientras que el dato de aplicación —usuarios, pedidos, sesiones— vive en la base de la plataforma, alcanzable desde el SSR a través de los bindings del adapter. La misma app lee un artículo de una colección y un pedido de una base sin que ninguna de las dos fuentes contamine a la otra.
---
// Una pagina del panel: dato de app desde el binding, sesion desde locals
export const prerender = false;
const { usuario } = Astro.locals;
const { results } = await Astro.locals.runtime.env.DB
.prepare('SELECT * FROM pedidos WHERE usuario = ?')
.bind(usuario.id)
.all();
---
<h1>Tus pedidos</h1>
<ul>{results.map((p) => <li>{p.referencia}</li>)}</ul>
flowchart TD V[Visitante] --> E[Edge del proveedor] E --> Z1[Zona publica estatica en CDN] E --> Z2[Catalogo estatico con server islands] E --> Z3[App SSR tras middleware de auth] E --> Z4[Endpoints para webhooks y movil] Z1 --> CL[Content layer colecciones tipadas] Z2 --> KV[KV para precio y stock] Z3 --> DB[D1 y sesiones en KV] Z4 --> DB style V fill:#89b4fa,color:#11111b style Z1 fill:#a6e3a1,color:#11111b style Z3 fill:#fab387,color:#11111b
Deploy al edge: coherencia entre render y runtime
La elección del adapter debe rimar con el diseño del render, no contradecirlo. Para este producto, un adapter de edge como el de Cloudflare cierra el círculo con elegancia: la zona pública estática se sirve desde el CDN global, el SSR del panel corre en Workers a milisegundos del usuario, las sesiones viven en KV, el dato de aplicación en D1, y los assets en R2.
// astro.config.mjs
import { defineConfig } from 'astro/config';
import cloudflare from '@astrojs/cloudflare';
export default defineConfig({
adapter: cloudflare(),
i18n: { defaultLocale: 'es', locales: ['es', 'en'] },
});
El build partido en dos
Un solo build produce dos artefactos complementarios, y el adapter sabe repartir cada petición entre ellos. La parte client son ficheros terminados que la plataforma sirve sin computar nada; la parte server es el manejador que ejecuta las rutas vivas cerca del usuario. Esta división es la que permite que estático y dinámico convivan sin estorbarse, cada uno servido por el mecanismo que le corresponde.
CDN
Sirve la parte client: el HTML horneado y los assets con hash, cacheados globalmente sin ejecutar tu código.
Workers
Ejecutan la parte server: el SSR del panel y los endpoints, a milisegundos del usuario en el edge.
KV
Guarda las sesiones y los datos cacheables de lectura rápida, cerca de donde corre el código.
D1 y R2
La base transaccional para pedidos y usuarios, y el almacén de objetos para imágenes y ficheros pesados.
Esta coherencia no es estética: es económica y de latencia. El contenido, que domina en volumen, no cuesta cómputo porque es estático y cacheado; la aplicación, que es minoría, corre cerca del usuario y ligera; el dato está donde el código lo alcanza rápido. Cambiar de proveedor sería, como aprendiste, mudar una importación y una clave, porque toda la especificidad vive comprimida en el adapter y el resto de tu código habla el lenguaje universal de la plataforma web.
Un sistema grande que no se observa se vuelve opaco justo cuando más importa entenderlo. El middleware de registro que pusiste al principio de la secuencia no era un adorno: es la fuente de la que sale una línea estructurada por petición, y con ella la latencia por ruta, la tasa de error por zona y el rastro de una sesión concreta. Diseñar dónde corre cada cosa incluye diseñar cómo sabrás, en producción, que cada cosa corre bien; la costura del middleware es el sitio natural para esa mirada.
Una app grande maneja claves de API, cadenas de conexión y tokens. Esos secretos no viven en el código ni viajan al cliente: se declaran como variables de entorno del runtime y se leen solo del lado servidor, en middleware, actions y endpoints. La frontera entre lo que se hornea y se envía al navegador y lo que queda en el servidor es también la frontera de la seguridad: nada sensible debe cruzarla hacia el cliente.
Si un solo hilo atraviesa los cuarenta niveles de este track y se anuda en este caso de estudio, es este: arquitectar no es elegir tecnologías, es ubicar verdades. Cada pieza que aprendiste responde, en el fondo, a la pregunta de dónde vive algo. Una colección tipada dice que el contenido vive en una fuente estructurada y validada, no disperso en el HTML. Una server island dice que un dato fresco vive separado de la página que lo enmarca, para no arrastrarla a lo dinámico. El middleware dice que lo que es cierto para toda petición vive en la costura por la que todas pasan, no copiado en cada ruta. Una action dice que una mutación vive en un handler validado en el servidor, no en la confianza ingenua de un formulario. El adapter dice que la dependencia del proveedor vive en una capa delgada y reemplazable, no impregnando cada fichero. Y el modo de render de cada página dice que su modo de existir —estático o dinámico, público o privado— vive determinado por su relación con el tiempo y con la identidad. Cuando pones todo junto en una app grande, no estás ensamblando un catálogo de features: estás construyendo un lugar coherente donde cada verdad —el contenido, el dato, la sesión, la mutación, el idioma, el secreto— reside exactamente donde su naturaleza pide residir, y por eso el sistema entero se explica solo. Un principiante mira esta arquitectura y ve muchas herramientas; un arquitecto ve una sola idea repetida con disciplina: cada cosa en su sitio ontológico. Ese es el nivel dios, y ya lo alcanzaste.
- Elige un producto grande y real —un marketplace, un medio digital, un SaaS— y repártelo en las cuatro zonas: pública, catálogo, app y API.
- Para cada zona, decide su modo de render, qué colecciones o bases de datos consume, y qué actions o endpoints necesita.
- Escribe la secuencia de middleware que gobierna sus flujos transversales —registro, seguridad, auth, i18n— y justifica el orden.
- Elige un adapter coherente con ese diseño, señala qué se sirve del CDN y qué corre en el servidor, y dónde viven las sesiones y los secretos.