Post-mortem: cuando un next build me tiró abajo el preview (incidente real, no de ejemplo)
La mayoría de los post-mortems que se publican son inventados para ilustrar un formato. Este no: es un incidente real del flujo de deploy de este mismo sitio, ocurrido mientras lo construía. No fue producción —no hubo usuarios afectados— pero un post-mortem no se mide por el tamaño del incidente, sino por el método. Y el método es el mismo a cualquier escala. Si querés el formato en detalle, lo desgloso en la anatomía de un post-mortem blameless; acá lo aplico a un caso concreto.
Reliability no empieza en producción. Empieza en el flujo con el que shippeás: si ahí hay fricción, se traslada a todo lo demás.
Contexto: ¿qué estaba pasando?
Estaba iterando sobre el sitio con el dev server (next dev) corriendo para previsualizar cambios en vivo. Para validar tipos antes de seguir, corrí en paralelo un next build (build de producción) sin apagar el dev server.
Detección: ¿qué alerta saltó?
No saltó una alerta automática — y esa ya es la primera lección. La "alerta" fue manual: el preview empezó a devolver errores y un chequeo de salud al server falló. En los logs del build apareció el síntoma:
Error [PageNotFoundError]: Cannot find module for page: /blog/[slug]
Error [PageNotFoundError]: Cannot find module for page: /blog
Timeline (minuto a minuto)
- T+0 — Corro
rm -rf .next && next buildconnext devtodavía activo. - T+0:30 — El build de producción reescribe
.next, el directorio que el dev server estaba usando en vivo. - T+1 — El preview empieza a servir páginas rotas:
PageNotFoundError. Unfetchal server falla. - T+2 — Diagnóstico: reviso procesos (
ps,lsof). El server sí está escuchando el puerto — no está caído, está sirviendo sobre un.nextcorrupto. Eso descarta "se murió el proceso" y apunta al estado compartido. - T+3 — Confirmo la causa: un
next buildcorrió sobre el.nextde unnext devvivo. - T+3:30 — Fix: stop del dev server →
rm -rf .next→ reinicio limpio. - T+4 — Preview sirviendo OK de nuevo (200 en todas las rutas).
Impacto: ¿a quién afectó?
Honestamente: cero usuarios de producción. Fue el entorno de desarrollo. Pero el impacto real fue de flujo: bloqueó la verificación de cambios por unos minutos y, peor, el preview mostraba páginas rotas que podían confundirse con un bug del código que estaba escribiendo. En un contexto de CI sin un chequeo posterior, el mismo patrón podría dejar pasar un artefacto roto.
Root cause: ¿cuál fue la causa raíz?
No fue "me equivoqué de comando". Eso es el síntoma. La causa raíz es de diseño del flujo: next dev y next build comparten el mismo directorio de estado (.next) sin ningún aislamiento ni guard. Nada impedía que un build de producción pisara el estado de un dev server en vivo. La culpa no es de la persona que corrió el comando; es del flujo que permitía la colisión en silencio.
Fix inmediato
Parar el dev server, borrar el .next contaminado y reiniciar:
# stop del dev server, luego:
rm -rf .next
npm run dev
Fix permanente: ¿qué cambié para que no vuelva a pasar?
- Regla de proceso: no correr
next buildcon el dev server activo. Elnext devya valida TypeScript al compilar cada ruta, así que el build de producción se reserva para cuando dev está apagado. - Aislamiento cuando hace falta validar en paralelo: un directorio de salida distinto (
next buildcon undistDirseparado) elimina la colisión de raíz — el equivalente a no compartir estado mutable entre dos procesos. - El gate va en CI, no a mano: la validación de tipos y el build de producción viven en el pipeline (GitHub Actions), aislados del entorno local. Validar a mano "por las dudas" fue lo que abrió la puerta al incidente.
Qué cambié en observability para detectarlo antes
La detección fue manual y tardía. Lo accionable: un smoke test después de cualquier build — un simple fetch a las rutas clave que devuelva el status. Es exactamente la etapa que el pipeline ya corre (Lighthouse CI carga las páginas reales antes de aprobar). La lección: si un humano es el primero en notar que algo está roto, falta una señal automática.
La lección de growth
Este fue un incidente chico, de entorno de desarrollo. Pero el principio escala: downtime no es solo un SLA roto, es conversión perdida — y la fricción en el flujo de deploy es velocidad de iteración perdida. Por eso trato la reliability, incluso la de mi propio tooling, como trabajo de growth: cada minuto que el sistema no está confiable es un experimento que no corrés, un cambio que no shippeás. Custodiar el crecimiento técnico de un producto es, en buena parte, hacer que estos incidentes sean aburridos: detectados rápido, entendidos a fondo, e imposibles de repetir.
¿Querés a alguien que opere tu producto con esta cabeza —que cuando algo falle lo entienda y lo cierre, en vez de solo apagar el fuego? Hablemos.