Dominá las variables de entorno en Next.js: El secreto de NEXT_PUBLIC_ y la seguridad de tus datos
Si alguna vez trabajaste con Next.js, seguro te topaste con un error frustrante: intentaste usar una API Key en un componente de React y te diste cuenta de que el valor era undefined, o peor aún, ¡expusiste una clave secreta de tu base de datos en el navegador de un usuario!
Manejar variables de entorno en un framework que mezcla ejecución en el servidor (Server Components, API Routes) con ejecución en el cliente (Client Components) tiene su ciencia. No es solo crear un archivo .env. Es entender dónde vive cada variable y quién tiene permiso para verla. En este post, te voy a enseñar a dominar este flujo para que tus aplicaciones sean escalables y, sobre todo, seguras.
¿Qué son las variables de entorno y por qué te importan?
Las variables de entorno son valores que configurás fuera del código fuente. En lugar de escribir const apiKey = '12345-abcde' directamente en tu archivo .ts, escribís const apiKey = process.env.API_KEY.
Esto es fundamental por tres razones:
- Seguridad: No subís credenciales sensibles al repositorio de Git.
- Flexibilidad: Usás una base de datos de pruebas en tu local y una de producción en Vercel sin cambiar una sola línea de código.
- Configuración dinámica: Podés cambiar comportamientos de la app según el entorno (development, staging, production).
En Next.js, la distinción crítica es el contexto de ejecución.
El gran divisor: Server-side vs. Client-side
Next.js utiliza un modelo híbrido. Esto significa que parte de tu código corre en Node.js (servidor) y otra parte corre en el navegador del usuario (cliente).
1. Variables de solo servidor (Server-only)
Por defecto, todas las variables que definas en tus archivos .env son privadas. Solo son accesibles en el entorno de ejecución del servidor. Esto incluye:
getServerSidePropsogetStaticProps(en el Pages Router).- Server Components (en el App Router).
- API Routes (
app/api/...). - Server Actions.
Si intentás acceder a una variable privada desde un componente que tiene la directiva 'use client', Next.js no te va a dar un error de compilación, pero el valor será undefined. Esto es una medida de seguridad para evitar fugas de datos.
2. Variables públicas (NEXT_PUBLIC_)
Si necesitás que una variable esté disponible en el navegador (por ejemplo, una URL de una API pública o una clave de Firebase que sea segura de exponer), tenés que anteponer el prefijo NEXT_PUBLIC_.
Cuando Next.js compila tu aplicación, busca todas las instancias de process.env.NEXT_PUBLIC_... y reemplaza físicamente ese texto por el valor real en el bundle de JavaScript que se envía al cliente.
⚠️ Advertencia crítica: Todo lo que tenga el prefijo
NEXT_PUBLIC_es visible para cualquiera que sepa abrir la consola del desarrollador en su navegador. Nunca, bajo ninguna circunstancia, pongas unSTRIPE_SECRET_KEYo unDATABASE_PASSWORDcon este prefijo.
Configuración paso a paso
Vamos a ver cómo estructurar esto en un proyecto real.
Paso 1: Creación de los archivos .env
En la raíz de tu proyecto, creamos archivos según el entorno. Next.js los carga automáticamente.
# .env.local (Para desarrollo local, NO se sube a Git)
DATABASE_URL="postgresql://user:password@localhost:5432/mydb"
STRIPE_SECRET_KEY="sk_test_51Mz..."
NEXT_PUBLIC_API_URL="http://localhost:3000/api"
NEXT_PUBLIC_ANALYTICS_ID="UA-12345678-9"Paso 2: Uso en un Server Component (Seguro)
Imaginemos que tenemos un componente que busca datos en la base de datos. Este componente se ejecuta solo en el servidor.
// app/dashboard/page.tsx
// Este es un Server Component por defecto
export default async function DashboardPage() {
// Accedemos a la variable privada
const dbUrl = process.env.DATABASE_URL;
// Simulamos una consulta a la DB
return (
<main>
<h1>Panel de Control</h1>
<p>Conectado a la base de datos: {dbUrl ? "✅ OK" : "❌ Error"}</p>
</main>
);
}Paso 3: Uso en un Client Component (Público)
Ahora, supongamos que necesitás la URL de la API para hacer un fetch desde el navegador.
'use client'; // Esto lo convierte en Client Component
import { useEffect, useState } from 'react';
export default function ClientConfigDisplay() {
const [apiUrl, setApiUrl] = useState<string>('');
const [secretKey, setSecretKey] = useState<string>('');
useEffect(() => {
// Intentamos leer ambas variables
setApiUrl(process.env.NEXT_PUBLIC_API_URL || 'No definida');
// Esto dará 'undefined' porque es privada
setSecretKey(process.env.STRIPE_SECRET_KEY || 'No definida');
}, []);
return (
<div className="p-4 border rounded">
<h3>Configuración del Cliente</h3>
<p><strong>URL de API (Pública):</strong> {apiUrl}</p>
<p><strong>Key Secreta (Privada):</strong> {secretKey}</p>
</div>
);
}Para que veas cómo se comporta esto en tiempo de ejecución, vamos a correr un pequeño script de simulación en Python que emule la lógica de resolución de Next.js.
import os
# Simulamos el entorno de Next.js
env_vars = {
"DATABASE_URL": "postgresql://admin:secret@localhost:5432/prod",
"STRIPE_SECRET_KEY": "sk_live_987654321",
"NEXT_PUBLIC_API_URL": "https://api.myapp.com",
"NEXT_PUBLIC_ANALYTICS_ID": "UA-999"
}
def get_env_var(key, is_client=False):
"""Simula el comportamiento de Next.js al acceder a variables"""
if is_client:
if key.startswith("NEXT_PUBLIC_"):
return env_vars.get(key, "undefined")
else:
return "undefined" # Seguridad: no expone privadas al cliente
else:
return env_vars.get(key, "undefined")
print("--- SIMULACIÓN DE ACCESO ---")
print(f"[SERVER] Accediendo a DATABASE_URL: {get_env_var('DATABASE_URL')}")
print(f"[SERVER] Accediendo a STRIPE_SECRET_KEY: {get_env_var('STRIPE_SECRET_KEY')}")
print("")
print(f"[CLIENT] Accediendo a NEXT_PUBLIC_API_URL: {get_env_var('NEXT_PUBLIC_API_URL', is_client=True)}")
print(f"[CLIENT] Accediendo a STRIPE_SECRET_KEY: {get_env_var('STRIPE_SECRET_KEY', is_client=True)}")Salida esperada del script:
--- SIMULACIÓN DE ACCESO ---
[SERVER] Accediendo a DATABASE_URL: postgresql://admin:secret@localhost:5432/prod
[SERVER] Accediendo a STRIPE_SECRET_KEY: sk_live_987654321
[CLIENT] Accediendo a NEXT_PUBLIC_API_URL: https://api.myapp.com
[CLIENT] Accediendo a STRIPE_SECRET_KEY: undefinedBuenas prácticas y errores comunes
Para que no te lleves sorpresas en producción, seguí estas reglas de oro:
✅ Qué SI hacer
- Usar
.env.localpara desarrollo: Este archivo debe estar siempre en tu.gitignore. Nunca lo subas. - Validar tus variables: Usá librerías como
zodpara validar que las variables de entorno existan al arrancar la aplicación. Si falta laDATABASE_URL, es mejor que la app falle al inicio a que falle cuando un usuario intente loguearse. - Tipado con TypeScript: Creá un archivo
env.d.tspara que cuando escribasprocess.env., el autocompletado te ayude y sepas qué variables tenés disponibles.
❌ Qué NO hacer
- No uses
NEXT_PUBLIC_para todo: Es una tentación ponerle el prefijo a todo para "que funcione", pero estás abriendo la puerta a que te roben credenciales. - No confíes en el cliente para lógica sensible: Si necesitás validar un token de usuario o procesar un pago, hacelo en una API Route o un Server Action. Nunca mandes la lógica de validación al navegador.
- No hardcodees valores: Si ves un string que parece una URL o una clave, movelo al
.envinmediatamente.
Resumen de la lección
Dominar las variables de entorno es la diferencia entre un desarrollador junior que "hace que funcione" y un profesional que construye aplicaciones seguras.
Recordá la regla de oro:
- ¿Se ejecuta en el servidor? Podés usar cualquier variable.
- ¿Se ejecuta en el navegador? Solo podés usar las que empiezan con
NEXT_PUBLIC_.
Si tenés dudas, siempre preguntate: "Si un usuario abre la consola de Chrome, ¿le debería ver este valor?". Si la respuesta es "no", quitale el prefijo NEXT_PUBLIC_.
¡Mucha suerte con tus próximos despliegues!
DUGLAS MORENO
Comentarios (0)
Sé el primero en comentar.