BookinglyTech News
Software

Un .env olvidado rompía 13 páginas del build de Next.js y el modo dev ni lo veía

El fallo aparecía en next build, no en npm run dev, y sobrevivía a un stash a commits viejos: el problema no estaba en el código, sino en una variable de entorno vacía.

2 min de lecturaDev.to0 vistas

Un next build fallaba siempre en las mismas 13 páginas con el mismo error: TypeError: Invalid URL, input: ''. En cambio, npm run dev arrancaba sin problemas. Un stash hasta commits de varias semanas atrás reproducía el fallo idéntico, así que lo que fuera aquello no estaba en el diff, ni en el historial de git, ni en una revisión de código.

La aplicación es un SaaS en Next.js 14 con App Router y NextAuth para el login por OAuth de GitHub. El layout raíz (app/layout.tsx) llama a getServerSession() envuelto en un try/catch añadido justo para que la ausencia de variables de entorno en un build estático no tumbara la página: registra el error, deja la sesión a null y sigue. La hipótesis era razonable. El código no era el que reventaba.

El primer intento fue mirar .env.local buscando algo mal escrito, empezando por NEXTAUTH_URL. Estaba y tenía valor. El segundo, sospechar de NEXTAUTH_URL_INTERNAL, la variable menos conocida de NextAuth para despliegues detrás de proxy: no aparecía por ningún lado. Dos conjeturas, dos fallos. A partir de ahí dejó de adivinar y fue a buscar la evidencia dura en la salida compilada.

El rastro estaba en el bundle

Un grep sobre .next/server/chunks/662.js buscando construcciones de new URL devolvió 19 coincidencias. Todas usaban solo tres identificadores: NEXTAUTH_URL, NEXTAUTH_URL_INTERNAL y VERCEL_URL. Eso no es código de la aplicación, es next-auth/react. La diferencia importa: un .env es un espacio de hipótesis, un bundle compilado es un registro de lo que el código hace de verdad.

El problema está en parseUrl, dentro de next-auth 4.24.15. La función construye una URL pasando url ?? defaultUrl, pero "" no es ni null ni undefined, así que la cadena vacía pasa el filtro sin caer al valor por defecto. Unos renglones antes, la comprobación url && !url.startsWith("http") también ignora la cadena vacía por ser falsy, de modo que nunca se le añade el prefijo https://. El resultado llega tal cual a new URL("") y lanza el TypeError. En algún punto, NEXTAUTH_URL valía "": no ausente, vacía.

El detalle que deja ko al try/catch: SessionProvider ejecuta parseUrl en el momento de importar el módulo, no dentro de una función que se llame después. Cuando el componente Providers va a renderizar, el módulo ya se evaluó y ya lanzó. No hay dónde capturarlo.

El origen del valor era un archivo .env sobrante de una sesión de depuración de un mes antes.

El caso deja un par de cosas que no se ven leyendo código: los archivos .env* no pasan por git ni por revisión, y una variable definida pero vacía no se comporta como una variable ausente. Cualquier proyecto que use next-auth y cargue NEXTAUTH_URL desde un entorno —CI, un gestor de secretos, un docker-compose— está expuesto al mismo error si el valor llega como cadena vacía en lugar de no llegar.