cavioca

Arquitectura

6 min

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

NavegadorclienteDNS Cloudflarecavioca.com / api.cavioca.comCloudflare Pagesapps/webNext 14 · edgeCloudflare Workerapps/apiHonoD1SQLite (Drizzle)R2objetos S3-compat

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, hacen fetch a apps/api y devuelven HTML.
  • API routes proxy — los handlers en apps/web/app/api/** reenvían a apps/api añ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:

  1. DNS resuelve cavioca.com al edge de Cloudflare más cercano.
  2. Pages recibe el request. Como /u/[username]/page.tsx es un Server Component dinámico con runtime = "edge", se ejecuta en el isolate del Worker de Pages.
  3. Pages → API — el Server Component hace fetch('https://api.cavioca.com/users/ricart').
  4. 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 = ?.
  5. D1 sirve la query. Como el isolate está geográficamente cerca, la latencia añadida es del orden de pocos ms.
  6. Worker → Pages devuelve JSON.
  7. 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.