Arquitectura
Cavioca corre todo sobre Cloudflare: el front en Pages, la API en Workers, los datos en D1 (SQLite) y el almacén binario en R2. No hay máquinas que mantener ni regiones que elegir — cada request aterriza en el edge más cercano al usuario.
Esta página describe las piezas y por dónde viaja un request real. Si te interesa el porqué de cada elección, salta a Stack y decisiones.
Mapa de piezas
Las piezas, una a una
apps/web — Cloudflare Pages
Una app Next.js 14 desplegada sobre Pages mediante
@cloudflare/next-on-pages.
Sirve el HTML, los Server Components y todo el JS de cliente. Casi todas las
rutas usan runtime = "edge" para correr en el mismo isolate que recibe el
request.
Tres caminos de renderizado conviven:
- Estático prerenderizado — landings, docs MDX, páginas
/about. Se cachean en el edge de Cloudflare y no tocan ningún backend. - Edge SSR — rutas dinámicas (
/u/[username],/a/[slug]). Corren en el Worker de Pages, hacenfetchaapps/apiy devuelven HTML. - API routes proxy — los handlers en
apps/web/app/api/**reenvían aapps/apiañadiendo cookies y headers de sesión.
apps/api — Cloudflare Worker (Hono)
El backend. Un único Worker construido con Hono,
desplegado vía wrangler desde apps/api/wrangler.toml. Expone:
/auth/*— passwordless por email (magic link, sesiones cookie)./users,/apps,/marketplace,/builder— el dominio./waitlist— captura pre-lanzamiento.
Cada handler valida la sesión, llama a Drizzle contra D1 y devuelve JSON.
D1 — SQLite gestionado
La base de datos. SQLite distribuido por Cloudflare. Las migraciones viven
en apps/api/db/migrations/sqlite/ (versiones .up.sql / .down.sql) y se
aplican con un runner en apps/api/db/src/migrations/runner.ts.
Hay también un mirror en apps/api/db/migrations/pg/ para desarrollo local
contra Postgres — útil si quieres iterar sin tocar D1 remoto. open
La paridad pg/sqlite se mantiene a mano por convención; no hay un test que
falle si una migración se añade sólo a un dialecto.
R2 — objetos
Todo lo que no cabe en una fila va a R2: avatares, banners de app, eventual
exportación de bundles. API S3-compatible, accedida desde el Worker con el
binding declarado en wrangler.toml.
DNS y certificados
cavioca.com→ Pages (apps/web).api.cavioca.com→ Worker (apps/api) vía custom domain.- TLS, HTTP/3 y caché están gestionados por Cloudflare; no hay configuración manual de certificados.
Anatomía de un request
Imagina que un usuario abre cavioca.com/u/ricart. Lo que ocurre, en orden:
- DNS resuelve
cavioca.comal edge de Cloudflare más cercano. - Pages recibe el request. Como
/u/[username]/page.tsxes un Server Component dinámico conruntime = "edge", se ejecuta en el isolate del Worker de Pages. - Pages → API — el Server Component hace
fetch('https://api.cavioca.com/users/ricart'). - Worker (apps/api) recibe el fetch. Hono lo enruta al handler, valida
la cookie de sesión (si existe), llama a Drizzle:
SELECT ... FROM users WHERE username = ?. - D1 sirve la query. Como el isolate está geográficamente cerca, la latencia añadida es del orden de pocos ms.
- Worker → Pages devuelve JSON.
- Pages → Navegador renderiza el HTML del perfil con los datos inyectados y lo devuelve al usuario.
Si la ruta hubiera sido estática (por ejemplo, una página de docs), los
pasos 3 a 6 desaparecen: Pages sirve el HTML cacheado directamente y el
request nunca toca apps/api ni D1.
¿Y los bindings?
Cloudflare expone D1, R2 y secrets como bindings que se inyectan en el
runtime del Worker (env.DB, env.BUCKET, env.SESSION_SECRET, …).
Declarados en apps/api/wrangler.toml. No hay variables process.env en
el sentido tradicional: si no está en el binding, no existe en runtime.
Gobierno: MeshKore
La planificación, las tareas, la roadmap y los logs de coordinación viven
fuera del código de producto, en .meshkore/. Está gestionado por
MeshKore — el estándar describe cómo
estructurar los módulos, tareas e iniciativas que ves en
/about/roadmap.
.meshkore/ está parcialmente ignorado por git: sólo .meshkore/public/
se commitea. El resto (estado, runtime, credenciales) vive en local.