30 de agosto de 2026 · En JLPTest
Las variables que se hornean en el build
Tres veces se desplegó una app que arrancaba perfecta y donde nadie podía iniciar sesión. Siempre por lo mismo, y nunca lo pareció.
En Next, una variable con prefijo NEXT_PUBLIC_ no se lee al arrancar: se sustituye por su valor mientras se compila. Si el build no la ve, lo que queda dentro del paquete del navegador es literalmente `undefined`, para siempre, hasta el siguiente build.
Y el build corría dentro de Docker, que por defecto no hereda las variables del panel de Railway. El resultado fue una app que pasaba todas las comprobaciones de salud —el contenedor arranca, el servidor responde, las páginas se pintan— y que por dentro estaba rota en tres sitios distintos:
- el sitemap salió apuntando a localhost, y así lo recogió el buscador;
- el muro de pago no se podía cerrar, porque el cliente de pagos nacía sin token;
- y el login no funcionaba: el cliente de Supabase era null, sin un solo error en los registros.
El arreglo evidente es declarar cada variable como ARG en el Dockerfile. Funciona, pero deja el mismo cepo puesto para el siguiente que añada una. Así que la app dejó de depender de eso: lo que el navegador necesita se lee en el servidor, ya en marcha, y baja como props.
// Sólo la llave pública viaja, y viaja en el HTML, no en el paquete.
export function configurarSupabase(url?: string | null, key?: string | null) {
if (!url || !key || configurado) return;
configurado = { url, key };
clienteNavegador = undefined; // que se cree de nuevo con lo bueno
}La regla que quedó: si cambiar una variable obliga a reconstruir la imagen, esa variable está en el lugar equivocado. Y la de seguridad que va con ella: una llave secreta jamás lleva ese prefijo, y los módulos que la usan importan `server-only`, para que colarla sea un error de compilación y no un incidente.