Secretos filtrados en Next.js: revisá tu bundle hoy
En pocas palabras: Sí: toda variable con prefijo NEXT_PUBLIC_ queda incrustada en el bundle de cliente servido desde /_next/static/, visible para cualquier visitante. El problema, documentado en DEV Community, afecta a apps de Next.js (el framework React de Vercel) que funcionan perfecto pero exponen credenciales privadas.
Todas las variables de entorno que marcás con NEXT_PUBLIC_ en Next.js terminan incrustadas en el JavaScript que cualquier visitante descarga. Un artículo publicado en DEV Community pone sobre la mesa el problema de los secretos filtrados en Next.js: apps que funcionan perfecto, pasan todos los tests y aun así exponen credenciales privadas en su bundle de cliente.
Next.js es un framework de React mantenido por Vercel para construir aplicaciones web con renderizado en servidor y en cliente. El prefijo NEXT_PUBLIC_ marca variables de entorno destinadas al navegador: durante la compilación, su valor queda incrustado dentro de los archivos JavaScript que se sirven desde /_next/static/. Si esa variable guarda una credencial privada, el secreto queda disponible para cualquiera que inspeccione el código de tu sitio.
En 30 segundos
- Todo lo que lleva
NEXT_PUBLIC_viaja al navegador. Durante la build, Next.js incrusta esas variables en el bundle descargable desde/_next/static/. - La app sigue funcionando aunque haya una fuga. No hay error en consola ni warning: el problema es de seguridad, no de funcionamiento.
- Claves como
sk_live_,DATABASE_URLoJWT_SECRETnunca van al cliente. Con ellas alguien puede autenticarse como tu aplicación o mover recursos a tu nombre. - El chequeo toma 90 segundos, según el procedimiento que propone Anas Sheikh en su artículo: DevTools, un archivo bajo
/_next/static/y Ctrl+F con patrones sospechosos. - Si encontraste una credencial expuesta, rotarla es obligatorio. Sacar el prefijo y redeployar no alcanza: la clave vieja ya circuló.
¿Por qué Next.js deja que los secretos lleguen al cliente sin avisarte?
Porque NEXT_PUBLIC_ es una instrucción de compilación, no una alerta de seguridad. Cuando marcás una variable con ese prefijo, le estás diciendo al compilador algo concreto: este valor puede entrar en el código que cada visitante descarga. El framework obedece durante la build y listo. Sin warning, sin error, sin cartel rojo.
Ponele este escenario: configurás tu .env con NEXT_PUBLIC_STRIPE_SECRET_KEY=sk_live_123456789, lo importás en un componente, deployeás, la página carga, el checkout cobra sin problemas, los tests pasan, nadie reporta nada raro, y mientras tanto tu clave secreta está a un Ctrl+F de distancia para el primero que abra DevTools en tu sitio, porque el valor viaja dentro del JavaScript que el browser baja sin pedir permiso a nadie.
¿Y algún cartel te avisa? Ninguno. Como resume Sheikh, el bug no rompe tu aplicación: rompe tu límite de confianza. La app puede seguir operativa durante meses con la fuga adentro.
¿Qué es el prefijo NEXT_PUBLIC_ y cuándo pasa de útil a catastrófico?
NEXT_PUBLIC_ es el convenio de Next.js para marcar variables que pueden exponerse al código cliente. Existe por una razón legítima: hay datos que el navegador necesita sí o sí. Una publishable key de Stripe (pk_live_...) fue diseñada para vivir en el browser, igual que tu ID de Google Analytics (G-...) o la URL pública de tu API.
El desastre arranca cuando la misma lógica se aplica a credenciales privadas. Mirá la diferencia entre estas dos líneas:
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=pk_live_...→ correcto, esa clave nació para el cliente.NEXT_PUBLIC_STRIPE_SECRET_KEY=sk_live_...→ exposición total de una credencial crítica.
Tema relacionado: desplegar tu app en Vercel sin costo.
Acá hay una creencia que conviene enterrar: “como está en el .env, es privado”. Falso. El archivo .env no tiene poderes mágicos; lo único que importa es dónde termina el valor. Y la regla mental que propone Sheikh es simple: no preguntes si tu frontend necesita ese dato, preguntate si publicarías ese valor en la portada de tu sitio. Si la respuesta es no, no va en JavaScript del cliente.
Una aclaración fina: saber la dirección de tu API (NEXT_PUBLIC_API_URL=https://api.example.com) no le da acceso a nadie. La URL puede ser pública. La credencial que la acompaña, no. Endpoint público no equivale a credencial privada.
Variables que nunca deben llevar el prefijo NEXT_PUBLIC_
Si exponer un valor permite suplantar a tu aplicación, firmar datos o acceder a recursos, ese valor no lleva el prefijo público. La tabla siguiente ordena los casos típicos:
| Variable | Ejemplo típico | ¿Puede ir en el cliente? | Riesgo si se filtra |
|---|---|---|---|
| NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY | pk_live_… | Sí | Ninguno: está diseñada para el navegador |
| NEXT_PUBLIC_GA_MEASUREMENT_ID | G-XXXXXXXXXX | Sí | Mínimo: es un identificador de medición |
| NEXT_PUBLIC_API_URL | https://api.example.com | Sí | Bajo: conocer la dirección no da acceso |
| STRIPE_SECRET_KEY | sk_live_… | Nunca | Cobros y control de la cuenta completa |
| DATABASE_URL / MONGODB_URI | postgresql://… / mongodb+srv://… | Nunca | Acceso directo a tu base de datos |
| JWT_SECRET | Cadena aleatoria | Nunca | Firmar o falsificar tokens válidos |
| RESEND_API_KEY | re_… | Nunca | Enviar correos desde tu cuenta |
| WEBHOOK_SECRET | whsec_… | Nunca | Suplantar eventos entrantes |
| AWS_SECRET_ACCESS_KEY | Clave de acceso AWS | Nunca | Crear o borrar recursos cloud |

Stripe merece un párrafo aparte porque su arquitectura tiene ambas llaves por diseño. La publishable va en el frontend, la secret en el servidor, y nunca se intercambian “porque el frontend necesita una key”. Necesita la publishable, que es otra cosa. Ya lo cubrimos antes en evitar caídas con una configuración DNS correcta.
Con las bases de datos es todavía más directo: tu browser no debería conocer jamás un connection string. Si el argumento es que el frontend necesita traer datos, la respuesta es que necesita los datos, no las credenciales para conseguirlos. Esa comunicación la resuelve tu servidor.
¿Cómo auditar tu bundle de producción en 90 segundos?
El chequeo que propone el artículo es manual, gratuito y aplicable hoy mismo a cualquier proyecto que tengas en producción:
- Abrí tu sitio real en producción, no el entorno local. La build de producción es la única que importa acá.
- Entrá a DevTools y filtrá la pestaña Network por JavaScript. Recargá la página para que aparezcan todos los recursos cargados.
- Buscá un archivo bajo
/_next/static/chunks/, por ejemplo uno con nombre tipomain-*.js. - Abrilo y usá Ctrl+F con patrones como
sk_live_,sk_test_,API_KEY,SECRET,password,postgresql://ymongodb. - Si hay coincidencia con una credencial real, la exposición está confirmada. Pasá a la sección de rotación más abajo.
Podés encontrar nada, y ese es el mejor desenlace posible. Ahora bien, el bundle no es el único lugar donde se cuelan secretos. En el repositorio conviene buscar patrones como sk_live_|API_KEY|SECRET_KEY|PASSWORD|TOKEN con grep (en Windows, PowerShell con Select-String cumple la misma función). Y ojo con las credenciales hardcodeadas: siguen siendo credenciales aunque estén en el código fuente, y el .gitignore no las hace privadas.
¿Ya expusiste una credencial? Rotarla es el único camino
Si una credencial real llegó al navegador, tratála como comprometida. Puede aparecer en bundles descargados, en caches intermedios o en la computadora de alguien que ya la vio, y no tenés forma de saber quién. El procedimiento completo: Complementá con proteger las variables de entorno en tu pipeline.
- Renombrá la variable sin el prefijo público para que quede del lado del servidor.
- Rotá la credencial en el dashboard del proveedor (Stripe, Resend, AWS o el servicio que sea) y generá una nueva.
- Actualizá el
.env.localo el panel de variables en Vercel con el valor nuevo. - Redeployá y verificá que la clave vieja ya no aparezca en ningún archivo del bundle nuevo.
- Investigá cuánto tiempo estuvo expuesta y qué pudo haber pasado en ese período.
El paso 2 es el que muchos fixes rápidos se saltan, y es justo el importante. Cambiar el nombre de la variable mejora el próximo deploy, pero no deshace el pasado.
¿Cómo estructurar tu app para que los secretos queden en el servidor?
El patrón correcto mantiene la credencial del lado del servidor y le devuelve al browser únicamente datos. El flujo queda así: el cliente llama a /api/data, el route handler del servidor lee DATABASE_URL, consulta la base y devuelve JSON. El navegador recibe el resultado. La credencial, nunca.
Los Server Components ayudan pero no son un escudo automático. Podés leer variables privadas en código de servidor sin drama, pero en cuanto le pasás información sensible como prop a un Client Component, cruzaste la línea: ese componente corre en el browser y todo lo que recibe debe considerarse visible. Lo mismo aplica con las Server Actions, que ejecutan en el servidor, así que la clave que usan no viaja al bundle.
Quedate con este modelo mental: si el navegador lo recibe, asumí que el usuario puede verlo. Minificar no lo oculta. Ofuscar tampoco. Encodearlo, menos. Escondido detrás de un componente React, menos todavía. Más contexto en qué herramienta de automatización te conviene.
Errores comunes que perpetúan las fugas
- Cambiar el nombre de la variable y redeployar sin rotar la clave. El bundle viejo sigue circulando; la credencial original sigue válida y sigue comprometida.
- Confiar en la ofuscación como “seguridad”. Un secreto minificado u encodeado es un secreto igual de legible para quien sabe dónde mirar.
- Pasarle el secret a un Client Component como prop. Que el dato entre por un Server Component no cambia nada: si llega al browser, es público.
- Creer que el frontend necesita las credenciales de la base. Necesita los datos; el acceso a la base es tarea exclusiva del servidor.
- Relajarse por el
.gitignore. Una clave hardcodeada en el código es tan expuesta como una variable mal prefijada.
Preguntas Frecuentes
¿Cómo me doy cuenta si mi app Next.js está filtrando secretos?
Abrí tu sitio en producción, la pestaña Network de DevTools, y buscá un archivo JS bajo /_next/static/. Descargalo y usá Ctrl+F con patrones como sk_live_, API_KEY, SECRET, password, postgresql:// o mongodb. Si aparece una coincidencia con una credencial real, confirmaste la exposición y corresponde rotar de inmediato.
¿Qué es NEXT_PUBLIC_ en Next.js y cuándo es peligroso?
NEXT_PUBLIC_ es el prefijo que le indica a Next.js que una variable de entorno puede incluirse en el bundle del cliente durante la compilación. Es seguro para valores públicos como URLs o IDs de analítica, y peligroso cuando guarda credenciales privadas, porque esas quedan visibles para cualquier visitante que inspeccione el JavaScript.
¿Puedo poner una API key en el cliente de Next.js?
Solo si la clave fue diseñada para el navegador, como una publishable key de Stripe (pk_live_...). Las secret keys (sk_live_...), las credenciales de base de datos y los tokens de autenticación deben quedar en el servidor, detrás de un endpoint propio que devuelva datos, nunca acceso.
¿Qué hago si ya expuse un secret en mi bundle?
Mové la variable al servidor sacándole el prefijo y, sobre todo, rotá la credencial en el dashboard del proveedor. El bundle anterior ya circula en caches y descargas ajenas, así que eliminar el prefijo sin rotar deja la puerta abierta. Después verificá que la clave vieja desapareció del build nuevo.
¿Minificar el JavaScript protege mis claves?
No. Minificar, ofuscar o encodear cambia la forma del código, pero el valor sigue siendo recuperable por quien lo busque. Si el navegador recibe el dato, asumí que el usuario puede terminar viéndolo.
Conclusión
El hallazgo de este artículo no señala un bug puntual sino un problema de límites de confianza: el browser es territorio hostil, y todo lo que le envíes debe considerarse potencialmente visible. Un prefijo de tres letras separa una variable pública de una credencial comprometida, y ninguna herramienta de build te va a reclamar cuando te equivoqués.
Antes de tu próximo deploy, dedicale los 90 segundos al chequeo del bundle. Da igual si deployeás en Vercel o en un hosting propio como donweb.com: el bundle se genera igual y el riesgo es idéntico. Si encontrás algo, rotá la credencial el mismo día. Encontrarlo vos hoy sale mucho más barato que descubrirlo por un dashboard de facturación inflado o por un mail incómodo de un cliente.






