Saltar al contenido
a@o:~$

cat /blog/phantom-build-failure.md

· 6 min · forense · next.js · windows · debugging

El build fantasma: cuando una mayúscula tira tu build de Next.js

Todas las páginas de un sitio completamente estático fallaron al prerenderizar con un invariante que culpaba al propio Next.js. El framework era inocente. Un recorrido forense refutando a los sospechosos obvios hasta que un solo carácter confesó.

síntoma

Un sitio recién creado en Next.js 16 — completamente estático, sin base de datos, dieciséis rutas — compiló limpio, pasó el type-check limpio, y murió durante el prerender. No una página intermitente: todas y cada una, incluyendo /_not-found y /_global-error, páginas que yo nunca escribí.

next build
Error occurred prerendering page "/_not-found".
Error [InvariantError]: Invariant: Expected workStore
to be initialized. This is a bug in Next.js.

Nota la última frase. El propio mensaje de error testifica que la culpa es del framework. Guarda ese pensamiento, porque la primera regla del forense de incidentes es que las confesiones ofrecidas tan temprano casi siempre son distracción.

evidencia

  • El dev server renderizaba las dieciséis rutas perfectamente. Solo el build de producción moría.
  • La falla era total, no parcial: las páginas generadas por el framework fallaban junto a las mías, lo que significa que el detonante estaba por debajo de mi código de aplicación.
  • El chunk que fallaba tenía el mismo hash de contenido en cada experimento que siguió — un detalle que parecía irrelevante hasta que dejó de serlo.

refutaciones

Data-first, refutación obligatoria: cada hipótesis se gana un experimento controlado, y el trabajo del experimento es matar la hipótesis, no halagarla.

  • «Mi código lo rompió.» Hice stash de todo y compilé el scaffold prístino de create-next-app. Falló idéntico. Mi código quedó absuelto.
  • «La versión de Next.js tiene un bug.» Bajé un patch release. Falla idéntica, hash de chunk idéntico.
  • «El runtime de Node es el problema.» El invariante huele a AsyncLocalStorage perdiendo contexto, que es territorio del runtime. Cambié de Node 20 a Node 24. Falla idéntica.

Tres sospechosos interrogados, tres coartadas confirmadas. Cuando toda explicación razonable queda refutada, la causa es algo que todavía no se te ocurre variar.

la pista

Next 16 todavía trae una salida de emergencia con webpack. El mismo build por un bundler distinto — no porque webpack fuera a arreglar nada, sino porque un segundo testigo describe el mismo crimen de otra manera. El testimonio de webpack:

next build --webpack
There are multiple modules with names that only
differ in casing.
 * C:\...\Desktop\Dev\portfolio\node_modules\next\...
 * C:\...\Desktop\dev\portfolio\node_modules\next\...

Ahí está. Dev y dev. La carpeta en disco lleva mayúscula; la sesión de la terminal había estado navegando con la ruta en minúsculas. El filesystem de Windows es case-insensitive, así que ambas grafías resuelven a los mismos bytes — pero el registro de módulos de Node indexa por el string de la ruta, y los strings sí distinguen mayúsculas.

causa raíz

Cada módulo importado por la ruta en minúsculas y el mismo módulo importado por la ruta con mayúscula se volvieron dos instancias separadas. Eso incluye el módulo que contiene el workAsyncStorage de Next.js — el AsyncLocalStorage que transporta el contexto de render. El build escribió el store en una copia y lo leyó de la otra. La lectura regresó vacía, y Next reportó, con total sinceridad, un bug en sí mismo.

El invariante no mentía; tenía mal el alcance. Era un bug en el proceso de Next.js — inyectado por un solo carácter de mi working directory.

intervención

Una línea: Set-Location C:\...\Desktop\Dev\portfolio con la grafía exacta del disco antes de compilar. Dieciséis de dieciséis páginas prerenderizadas. Costo total del defecto: una letra mayúscula. Costo total de encontrarlo sin un método: ilimitado.

lecciones

  • Los mensajes de error asignan culpas adivinando. Trata «esto es un bug en X» como una afirmación por verificar, no como un veredicto que aceptar.
  • Reproduce sobre una línea base prístina antes de tocar tu propio código. Es el experimento más barato con el mayor rendimiento de información.
  • Cuando una herramienta es opaca, corre la misma operación por una herramienta hermana. Dos renderizados distintos de la misma falla triangulan la causa.
  • Filesystems case-insensitive con runtimes case-sensitive son un peligro permanente en Windows y macOS. Si aparece rareza de identidad de módulos — singletons duplicados, contexto perdido, React doble — audita primero la grafía de tus rutas.