wandres.dev
NIVEL DIOS: SÍNTESIS · el toolchain unificado

Caso de estudio: de cero a producción al edge

Un recorrido integral que ensambla todo el track en un solo viaje: de una carpeta vacía a una app servida al edge, con pnpm workspaces y catalogs, una librería compartida, Vite sobre Rolldown, Turborepo con caché remota y un pipeline de CI reproducible. Cada paso muestra qué decisión de las lecciones anteriores se materializa en un archivo concreto.

⏱ 22 min

Toda la teoría del track converge aquí, en un solo viaje concreto: de una carpeta vacía a una app servida al edge, a milisegundos del usuario. No introduciremos ideas nuevas; materializaremos las que ya conoces en los archivos exactos donde viven. Verás cómo la estructura del repo se vuelve un pnpm-workspace.yaml, cómo el grafo de tareas se vuelve un turbo.json, cómo la caché remota se vuelve dos variables en CI y cómo el build de producción se vuelve un artefacto que un CDN replica por el mundo. Sigue el recorrido leyendo cada archivo como la respuesta física a una decisión de arquitectura, y al final tendrás el mapa completo grabado no en la memoria, sino en las manos.

🎯 Al terminar esta lección sabrás
  • Ensamblar un monorepo real con pnpm workspaces, catalogs y una librería compartida.
  • Cablear el grafo de tareas con Turborepo y verlo cachear localmente.
  • Conectar la caché remota y un CI reproducible con frozen-lockfile.
  • Desplegar el build de producción al edge y cerrar el ciclo del commit al CDN.

El esqueleto: repo, workspaces, catalogs

Empezamos por la decisión que gobierna todo lo demás: la estructura. Elegimos monorepo porque la app y su librería de UI evolucionarán juntas. pnpm lo materializa con un archivo en la raíz que declara dónde viven los paquetes y —clave a escala— un catalog que fija las versiones compartidas en un solo lugar.

# pnpm-workspace.yaml: la estructura y las versiones canonicas
packages:
  - "apps/*"
  - "packages/*"
catalog:
  react: ^19.0.0
  react-dom: ^19.0.0

Cada paquete referencia el catálogo con catalog: en vez de repetir el número de versión. Así, actualizar React es cambiar una línea en la raíz, no cazar versiones divergentes por diez package.json. La app, además, consume la librería interna con el protocolo workspace:, que enlaza al código local sin publicar nada.

// apps/web/package.json: consume el catalogo y la lib interna
{
  "name": "web",
  "dependencies": {
    "react": "catalog:",
    "react-dom": "catalog:",
    "@acme/ui": "workspace:*"
  }
}
# Instalar una vez, desde la raiz, con el store enlazado de pnpm
pnpm install

Las tareas: Vite y Turborepo

La app se construye con Vite, que en 2026 trae Rolldown sobre Oxc como motor por defecto: dev server ESM sin empaquetar para el arranque instantáneo, y un build de producción con tree shaking, splitting y minificación nativos. La configuración es deliberadamente mínima.

// apps/web/vite.config.ts: el minimo, el motor ya es Rolldown
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'

export default defineConfig({
  plugins: [react()],
  build: { sourcemap: true }
})

Con dos paquetes ya aparece el problema de escala: la app depende de la librería, así que construir en el orden correcto y no rehacer lo que no cambió deja de ser trivial. Turborepo lo resuelve declarando el grafo de tareas una sola vez.

// turbo.json: el grafo de tareas y las cajas para cachear
{
  "tasks": {
    "build": {
      "dependsOn": ["^build"],
      "inputs": ["src/**", "package.json", "vite.config.ts"],
      "outputs": ["dist/**"]
    },
    "test": { "dependsOn": ["build"] }
  }
}

El dependsOn con ^build le dice a Turbo que antes de construir la app debe construir sus dependencias. Pide el objetivo y el grafo decide el orden y qué saltar.

# La primera vez construye todo; la segunda, cache local en milisegundos
pnpm turbo build
pnpm turbo build   # FULL TURBO: nada cambio, replica la salida cacheada
💡
El affected es tu multiplicador

En cuanto el repo crece, deja de construir todo en cada cambio. Turbo puede ejecutar solo las tareas afectadas por lo que tocaste comparando contra una referencia de git. Un cambio en la librería reconstruye la app que la consume; un cambio en el README no reconstruye nada. Ese filtrado —turbo build --affected— es lo que mantiene el pipeline en segundos aunque el monorepo tenga cien paquetes. La condición para que sea correcto es que el grafo de dependencias sea fiel: si una dependencia real no está declarada, el affected la ignora y despliegas algo a medio construir.

La red: caché remota y CI

La caché local acelera tu máquina, pero el trabajo no viaja: tu compañera y el CI recompilan lo que tú ya compilaste. La caché remota rompe ese muro. El artefacto que produjiste esta mañana lo restaura el CI esta tarde por su hash, sin recompilar. Se conecta con dos variables.

# CI: instala reproducible y reutiliza la cache remota por hash
env:
  TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
  TURBO_TEAM: ${{ vars.TURBO_TEAM }}
steps:
  - run: pnpm install --frozen-lockfile
  - run: pnpm turbo build test

El --frozen-lockfile es la garantía de reproducibilidad: CI falla si el lockfile no cuadra con el package.json, en vez de resolver versiones nuevas a tus espaldas. Es la diferencia entre “en mi máquina funcionaba” y un build idéntico bit a bit en cualquier parte.

flowchart LR
dev[Local: pnpm turbo build] --> push[git push]
push --> ci[CI: pnpm install frozen-lockfile]
ci --> cache{Hash en cache remota}
cache -->|hit| restore[Restaura sin recompilar]
cache -->|miss| build[Construye y sube la salida]
restore --> edge[Deploy al edge]
build --> edge
style dev fill:#89b4fa,color:#11111b
style cache fill:#fab387,color:#11111b
style edge fill:#a6e3a1,color:#11111b

El destino: deploy al edge

El build de producción de la app es una carpeta dist de archivos estáticos con nombres hasheados —app.9f2c.js— listos para cachearse para siempre en un CDN. Desplegar al edge es subir esa carpeta a una red que la replica cerca de cada usuario del mundo, de modo que la latencia la fija la geografía, no tu servidor.

# El artefacto ya existe en dist; el deploy solo lo distribuye al edge
pnpm turbo build --filter=web
pnpm --filter=web deploy   # sube dist al CDN y publica la nueva version

Los nombres hasheados cierran el círculo con la caché del navegador: como el contenido de app.9f2c.js no cambia nunca —si cambia, cambia el hash y con él el nombre—, el CDN y el navegador pueden cachearlo de forma inmutable. Un despliegue no invalida caché a mano; publica archivos nuevos con nombres nuevos y el index.html apunta a ellos.

El pipeline entero es una sola idea aplicada cinco veces: no repitas trabajo

Repasa el viaje que acabas de recorrer y verás que, bajo cinco herramientas distintas, late una sola idea: no repetir trabajo que ya está hecho. El store de pnpm no vuelve a descargar un paquete que ya tiene; lo enlaza desde un almacén global, así que cien proyectos comparten una copia. Los catalogs no repiten un número de versión por diez paquetes; lo declaran una vez y todos lo heredan. Rolldown no reparsea tu código para cada herramienta; comparte un AST de Oxc de punta a punta. Turborepo no reconstruye lo que no cambió; hashea las entradas y replica la salida cacheada, y con affected ni siquiera mira los paquetes que no tocaste. Y el hashing de assets no reinvalida la caché del navegador en cada deploy; nombra los archivos por su contenido para que lo inmutable se cachee para siempre. Cinco capas, cinco herramientas, una sola filosofía: el trabajo más barato es el que no se hace. Esta es la síntesis que llevarte del track entero. Durante años la carrera del tooling fue “compilar más rápido”, de webpack a esbuild a Rolldown, y cada salto redujo los tiempos en un orden de magnitud. Pero hay un techo, y el build más rápido es el que no ocurre. Por eso el arquitecto maduro no mide su pipeline por lo rápido que compila, sino por cuánto logra no compilar: cuántos cache hits, cuántos paquetes que el affected deja fuera, cuántos assets que el navegador ya tiene. Montar este pipeline no fue configurar cinco herramientas inconexas; fue aplicar cinco veces el mismo principio en capas distintas, de forma que se compongan. Cuando lo interiorizas, dejas de ver herramientas y empiezas a ver una sola disciplina —la frugalidad del trabajo repetido— que puedes llevar a cualquier stack que venga después, porque el principio sobrevive a las herramientas que hoy lo encarnan.

⚔️ Lleva tu propia app de cero al edge
  1. Crea un monorepo con pnpm workspaces, una apps/web con Vite y una packages/ui consumida con workspace:; unifica React con un catalog.
  2. Escribe el turbo.json, ejecuta pnpm turbo build dos veces y confirma el FULL TURBO de la segunda: la caché local en acción.
  3. Toca solo la librería y ejecuta turbo build --affected --dry-run para verificar que reconstruye la app pero no lo ajeno.
  4. Conecta la caché remota con TURBO_TOKEN y comprueba un cache hit desde una máquina o CI distintos de donde compilaste.
  5. Despliega el dist al edge, inspecciona los nombres hasheados de los assets y explica por qué permiten caché inmutable en el navegador.