Este ensayo se publico primero en brianmenagomez.com/blog/el-flag-que-rompe-el-build.
El flag que rompe el build
Activar `--turbopack` debería haber sido un cambio de una línea. Es lo que leía en la documentación de Next.js: el bundler nuevo, escrito en Rust, más rápido que webpack, empujado como su reemplazo natural. En mi cabeza la cuenta era sencilla. Pongo el flag, el build tarda menos, y me olvido. Lo puse. El build cayó.
No cayó una vez. Cayó cada vez, en el mismo punto, con el mismo error. Quitaba el flag y compilaba sin problemas. Lo volvía a poner y crasheaba otra vez, idéntico. Repetí el ciclo unas cuantas veces por pura incredulidad: el resultado no cambió en ninguna. Un fallo que se repite de forma determinista deja de ser un misterio: se convierte en un dato. Y el dato era incómodo: Sanity, el CMS con el que gestiono el contenido de Conversor, no soporta Turbopack.
Lo que estaba en juego no era un detalle cosmético. El build es lo que empaqueta el sitio que sirve los datos fiscales y la API. Un build roto significa una página caída y unos docs inaccesibles. Por eso me lo tomé como un problema de verdad y no como una curiosidad de bundler.
Lo que no pude arreglar
Aquí está la parte que más me costó aceptar. No es un bug que pueda arreglar desde mi código. Yo no escribo el plugin de Sanity; solo lo consumo. La incompatibilidad vive en una dependencia de terceros, y ahí no pinto nada. Tenía dos caminos. Perseguir el fallo durante días — probar versiones, abrir issues, leer código ajeno — o aceptar el dato y decidir.
Decidí lo corto. El build de producción usa siempre webpack: `npm run build`, sin flag. No es la opción más rápida, pero es la que no se rompe. Cuando despliegas cada semana, un build un poco más lento es un precio razonable por no levantarte con un deploy caído por culpa de una dependencia que no controlas.
Pero una decisión tomada una vez no basta, porque yo no soy el único que toca este repositorio.
La defensa en profundidad
Tengo un operador nocturno: un agente autónomo que corre tareas de mantenimiento mientras duermo. Ese agente podría reintroducir `--turbopack` de buena fe. Porque lo leyó en la documentación, porque es lo que se recomienda, porque en las instrucciones que le dejé nadie le contó lo del crash. No me fío de mi memoria para impedirlo, y no me fío de la suya.
Así que lo bloqueé de forma determinista, en dos capas.
La primera es `permissions.deny`, la lista de permisos que el agente no puede usar. Ahí, ese flag no existe: está denegado antes de que nada se ejecute. La segunda es `guard.mjs`, un hook que corre en cada paso y frena el flag antes de que llegue a tocar nada. Son dos mecanismos distintos, en dos puntos distintos del flujo: uno a nivel de permiso, otro a nivel de ejecución. Si una capa falla, queda la otra. Es defensa en profundidad, y el motivo es concreto: quiero que el flag no pueda volver a entrar por descuido, pase lo que pase. La redundancia no es paranoia; es lo que convierte un "no debería pasar" en un "no puede pasar".
Esto es la diferencia entre esperar que no ocurra y hacer que no pueda ocurrir. Yo ya no espero.
El build local no sirve como gate
La segunda lección tiró abajo un hábito que daba por bueno. Durante un tiempo verifiqué todo en mi máquina antes de subir nada: `npm run build` local, y si estaba en verde, adelante. El problema es que en Windows ese build local está roto para los códigos con asterisco. Los códigos IAE y CNAE con los que trabajo llevan `*`, y el build se atraganta con ellos en mi entorno. El resultado era un gate que me daba falsa seguridad o falsas alarmas, dependiendo del día, y que además no reflejaba lo que iba a pasar en producción.
Dejé de hacer gate en el build local. La corrección del build de producción se verifica solo en la preview de Vercel. Si la preview compila, el build está bien. Si no, no lo está. No hay grises. La preview pasó a ser la fuente de verdad, y el build local quedó como una comprobación temprana que no siempre está disponible y en la que ya no baso ninguna decisión.
tsc en verde no es next build en verde
La tercera lección es la que más me resistí a aceptar: un `tsc` en verde no prueba que un `next build` esté en verde. Son dos comprobaciones distintas, y yo las trataba como una sola.
El chequeo de tipos de TypeScript valida que los tipos cuadran: que una función devuelve lo que declara, que un objeto tiene los campos que debe. El build valida que el proyecto entero empaqueta: el bundler, los plugins, el CMS, todo junto y en orden. Puedo tener tipos perfectos y un build que crashea. Lo sé porque me pasó: `tsc` pasaba sin un solo error, y Sanity seguía cayendo con el flag. Eran dos preguntas distintas y yo solo miraba la primera creyendo que respondía por las dos.
Qué aprendí
Lo que me llevo es más simple de lo que parece. Cuando un fallo es determinista y no es mío, no lo persigo: lo bloqueo, y lo bloqueo dos veces. Y ya no me fío de una comprobación como prueba de otra. El chequeo de tipos responde a una pregunta, el build responde a otra, la preview responde a una tercera. Cada una necesita su verificación, y ninguna sustituye a las demás.
Lo demás — webpack en vez de Turbopack — es solo una línea en la configuración y dos capas de guardarraíl que me dejan dormir tranquilo.
Lo que publica ese build, si quieres verlo: https://www.conversoriaecnae.es/api/v1/docs

