6  F6. De dev server a build: qué produce tu frontend

npm run dev no es tu app — es la fábrica que la construye

NotaEn una frase

npm run dev levanta un servidor de desarrollo que compila tu código al vuelo; npm run build produce archivos estáticos (HTML+JS+CSS) que un servidor cualquiera puede servir. Entender esa diferencia es entender cómo el frontend llega a producción — y cómo se conecta con tu backend.

6.1 El problema

En desarrollo todo funciona en localhost:5173 con Vite recargando solo. Luego alguien pregunta: “¿y en producción cómo corre?” — y la respuesta sorprende: tu React no corre en el servidor. Se construye a archivos estáticos y cualquier servidor (nginx, un CDN, tu propio backend) los sirve tal cual. El navegador del usuario hace todo el trabajo.

Una CDN (Content Delivery Network) es una red de copias: tu estático se cachea en servidores cercanos al usuario. El de Buenos Aires no viaja a Virginia por una imagen — la recibe del nodo local.

Producción (prod) es el entorno real: donde están los usuarios, los datos que importan y las consecuencias. Todo lo demás — local, staging — existe para que los errores ocurran antes de llegar ahí.

localhost significa “esta misma máquina” — es la dirección que tu computadora usa para hablarse a sí misma. Cuando desarrollas, el “servidor” y el “cliente” viven en tu laptop: por eso todo es localhost:3000.

6.2 Conceptos nuevos

  • U Dev server: compila al vuelo, hot reload, solo para desarrollar — nunca para usuarios.
  • U Build: npm run build → dist/ con archivos estáticos optimizados (minificados, con hash).

Un hash es una función de un solo sentido: contraseña →$2b\(10\)…`. No se puede revertir — por eso las contraseñas se hashean, no se cifran. bcrypt es lento a propósito: fuerza bruta cara para el atacante.

  • U SPA vs SSR: la app corre en el navegador (SPA) vs el servidor prerenderiza HTML (SSR).
  • U API vs static hosting: tu frontend son archivos; tu backend es un proceso — dos cosas distintas a desplegar.

Un proceso es un programa en ejecución: el sistema operativo le da memoria propia y un lugar en la fila del CPU. Tu API es un proceso; Postgres es otro; el navegador es varios. Que “se caiga el proceso” = el programa murió.

6.3 La explicación visual

flowchart LR
  subgraph Dev["Desarrollo"]
    D1["npm run dev<br/>Vite dev server :5173"] -->|"compila al vuelo"| B1["navegador"]
  end
  subgraph Prod["Producción"]
    B["npm run build"] --> S["dist/ = index.html<br/>+ app.js + style.css"]
    S --> N["nginx / CDN / tu backend<br/>los sirve estáticos"] --> B2["navegador del usuario"]
    B2 -->|"fetch /api"| API["tu backend (Sección II)"]
  end

La lección clave: en producción hay dos servicios — el que sirve los archivos estáticos (frontend) y el que responde /api (backend). Pueden ser el mismo proceso o dos distintos.

6.4 Implementación

npm run dev      # Vite dev server — solo desarrollo, hot reload
npm run build    # → dist/ : index.html + assets/*.js + *.css minificados
npm run preview  # sirve dist/ localmente para probar el build

Mira lo que genera build:

El build (construcción) es el proceso que convierte tu código fuente en algo ejecutable/entregable: compilar TypeScript a JS, empaquetar el frontend, armar la imagen Docker. Lo que se despliega es el resultado del build, no tu código crudo.

dist/
├── index.html              # el esqueleto que carga todo
└── assets/
    ├── index-a1b2c3.js     # TODO tu React, minificado, con hash
    └── index-d4e5f6.css    # tus estilos, minificados

El hash en el nombre (a1b2c3) es caché-busting: cambia cuando el código cambia, así el navegador sabe cuándo re-descargar.

Un caché es una copia rápida de algo costoso de obtener: el resultado de una query pesada, la sesión. Redis es el caché estándar. La regla de oro: cachear es fácil, invalidar (saber cuándo el caché ya no vale) es lo difícil.

Explícame la diferencia entre `npm run dev`, `npm run build` y
`npm run preview` en un proyecto Vite + React. Quiero saber: qué genera
el build físicamente, por qué el servidor de producción solo sirve
archivos estáticos, y cómo se conecta el frontend desplegado con un
backend en /api. Usa un ejemplo con nginx o sirviendo dist/ desde el
propio backend.

Esta decisión es material: entregar tu UI al navegador debe pasar; cómo llega ahí tiene variantes reales.

Alternativa Qué es Qué te cuesta Cuándo elegirla
SPA estática (este capítulo) Build → archivos servidos por cualquiera SEO más difícil, primera carga vacía Apps privadas, dashboards — el default
SSR (Next.js, Nuxt, Angular Universal) El servidor prerenderiza HTML por request Un proceso Node corriendo siempre SEO, contenido público, e-commerce
SSG (static site generation) HTML generado en build por página Rebuild al cambiar contenido Blogs, docs, marketing
Server components (React moderno) Componentes que corren solo en servidor Modelo mental nuevo, framework-specific Apps grandes con Next.js

6.5 Errores comunes

  • Desplegar el dev server: npm run dev en producción — está hecho para velocidad de desarrollo, no para usuarios.
  • Pensar que el frontend “corre” en el servidor: solo se sirven archivos; el código corre en el navegador del usuario.
  • API hardcodeada a localhost: fetch("http://localhost:8000") en el build de producción — la base URL debe venir del entorno.

Un entorno es una instancia completa donde corre tu app con su propia config y datos: local (tu máquina), staging (réplica de prueba), producción (la real). Cada entorno tiene sus propias llaves y su propia base de datos.

6.6 Buenas prácticas

  • dist/ a un estático (nginx, CDN, S3) — el frontend no necesita un proceso vivo, solo archivos servidos.
  • La API como variable de entorno: VITE_API_URL — el build se adapta a cada entorno sin re-compilar lógica.

Un compilador traduce código a otra forma: TypeScript → JavaScript (transpila), Go → binario (compila). Atrapa errores antes de ejecutar. tsc, esbuild, go build son compiladores.

  • Mismo dominio si puedes: servir frontend y /api desde el mismo origen evita CORS (Sección II lo explica).

CORS es la política del navegador: por defecto, una página de sitioA.com no puede leer respuestas de apiB.com. El servidor declara qué orígenes acepta. Es protección del navegador, no del servidor (curl ni la ve).

6.7 Ejercicio

  1. Corre npm run build en tu app Vite y mira dist/ — encuentra el JS con hash y confirma que index.html lo referencia.
  2. Sirve dist/ con npm run preview y verifica que funciona sin el dev server.

Un servidor es una computadora que espera peticiones y las responde 24/7. Físicamente no es nada mágico: es una máquina (a veces una VM alquilada) corriendo tu programa, con la diferencia de que está siempre encendida y conectada.

6.8 Mini reto

Si el frontend son solo archivos estáticos, ¿cómo “sabe” a qué backend llamar? Diseña la respuesta — pista: variable de entorno en build o mismo-origen con proxy.

Una variable es una caja con nombre donde guardas un valor: let total = 42 guarda el 42 bajo el nombre total. Puedes leerla y cambiarla después (total = 50). const = caja que no se puede reemplazar.

Un reverse proxy (nginx, ALB, Cloudflare) es el portero: recibe todo el tráfico en el 443, termina TLS y reparte a tu app interna. La app nunca mira a internet directamente — el proxy la protege.

Una variable de entorno es configuración que vive fuera del código: DATABASE_URL, JWT_SECRET. El mismo binario corre en dev y prod con distintas vars — los secretos nunca se escriben en el código.

Ejercicio 1. dist/index.html referencia assets/index-<hash>.js — un solo archivo con todo tu código minificado; el hash cambia al reconstruir.

Mini reto. Dos caminos: (a) VITE_API_URL inyectada en el build (import.meta.env.VITE_API_URL) apunta al backend por entorno; (b) mismo-origen — el servidor que sirve dist/ también responde /api/* (nginx proxy o el propio backend sirviendo estáticos), y el frontend usa fetch("/api/...") sin host. El (b) es el más simple en producción y evita CORS por completo.

6.9 Vocabulario técnico del capítulo

Una clase es el molde de un objeto: define qué datos y qué métodos tiene. class Task es el molde; new Task() es una instancia concreta. Go no tiene clases — usa struct + métodos sueltos.

Un dominio es el nombre que compras (midominio.com) y apuntas a tu servidor por DNS. Sin él, tus usuarios tendrían que memorizar una IP. El HTTPS serio requiere dominio.

Un bucket (S3, Azure Blob, GCS) es almacenamiento de archivos como servicio: subes un PDF/imagen, te devuelve una URL. No es un disco — no lo montas, lo consultas por HTTP. Barato, infinito, durable.

Una traza sigue un request a través de todo el sistema: entró por el gateway → llamó auth → consultó la BD → tardó 340ms en la query. Cuando algo anda lento, la traza dice exactamente dónde.

Un framework es un esqueleto de aplicación ya decidido: te da la estructura (rutas, validación, errores) y tú llenas la lógica. Diferencia con librería: la librería la llamas tú; el framework te llama a ti.

El dev server (Vite, npm run dev) sirve tu app mientras desarrollas y recarga el navegador al guardar — hot reload. No es el servidor de producción: es la mesa de trabajo.

6.10 Lo que deberías saber hacer ahora