Variables de Entorno
Buenas practicas para manejar secretos y variables de entorno
Variables de Entorno
Un secreto filtrado puede comprometer toda tu aplicación: acceso a la base de datos, cobros fraudulentos con Stripe, envio de emails en tu nombre. Sigue estas practicas para evitarlo.
Regla de oro
Nunca subas archivos .env a Git. Nunca expongas secretos del servidor en el cliente.
.env.local y .gitignore
El archivo .env.local contiene tus secretos de desarrollo. Verifica que este en .gitignore:
# Variables de entorno
.env
.env.local
.env.*.localEl boilerplate incluye .env.example con las variables necesarias pero sin valores reales:
# App
NEXT_PUBLIC_APP_URL="http://localhost:3000"
# Database
DATABASE_URL=""
# Better Auth
BETTER_AUTH_SECRET=""
BETTER_AUTH_URL="http://localhost:3000"
# Stripe
STRIPE_SECRET_KEY=""
STRIPE_WEBHOOK_SECRET=""
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=""
# Resend
RESEND_API_KEY=""Servidor vs Cliente
Next.js tiene una regla simple pero critica:
| Prefijo | Accesible en | Seguridad |
|---|---|---|
NEXT_PUBLIC_ | Cliente + Servidor | Visible para cualquiera |
| Sin prefijo | Solo Servidor | Protegido |
Variables públicas (cliente)
Solo estas variables deben tener el prefijo NEXT_PUBLIC_:
NEXT_PUBLIC_APP_URL- URL de tu aplicaciónNEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY- Clave publica de Stripe
Estas variables se incluyen en el bundle de JavaScript del navegador. Cualquier usuario puede verlas.
Variables privadas (servidor)
Todas las demas variables son privadas y solo accesibles en Server Components, API Routes y Server Actions:
DATABASE_URLSTRIPE_SECRET_KEYSTRIPE_WEBHOOK_SECRETBETTER_AUTH_SECRETRESEND_API_KEYGOOGLE_CLIENT_SECRET
Si accidentalmente agregas NEXT_PUBLIC_ a una variable secreta, esa variable se expone en el navegador. Revisa esto con cuidado.
Validar variables al iniciar
No esperes a que falle una peticion para descubrir que falta una variable. Valida al arrancar:
import { z } from "zod";
const envSchema = z.object({
// Publicas
NEXT_PUBLIC_APP_URL: z.string().url(),
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY: z.string().startsWith("pk_"),
// Privadas
DATABASE_URL: z.string().min(1, "DATABASE_URL es obligatoria"),
STRIPE_SECRET_KEY: z.string().startsWith("sk_"),
STRIPE_WEBHOOK_SECRET: z.string().startsWith("whsec_"),
BETTER_AUTH_SECRET: z.string().min(16, "BETTER_AUTH_SECRET debe tener al menos 16 caracteres"),
BETTER_AUTH_URL: z.string().url(),
RESEND_API_KEY: z.string().startsWith("re_"),
});
export const env = envSchema.parse({
NEXT_PUBLIC_APP_URL: process.env.NEXT_PUBLIC_APP_URL,
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY: process.env.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY,
DATABASE_URL: process.env.DATABASE_URL,
STRIPE_SECRET_KEY: process.env.STRIPE_SECRET_KEY,
STRIPE_WEBHOOK_SECRET: process.env.STRIPE_WEBHOOK_SECRET,
BETTER_AUTH_SECRET: process.env.BETTER_AUTH_SECRET,
BETTER_AUTH_URL: process.env.BETTER_AUTH_URL,
RESEND_API_KEY: process.env.RESEND_API_KEY,
});Importa env en vez de usar process.env directamente:
// Mal
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!);
// Bien
import { env } from "@/lib/env";
const stripe = new Stripe(env.STRIPE_SECRET_KEY);Generar secretos seguros
# Para BETTER_AUTH_SECRET
openssl rand -base64 32
# Alternativa con Node.js
node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"Genera un secreto nuevo para cada entorno (desarrollo, staging, producción). Nunca reutilices secretos entre entornos.
Checklist de seguridad
.env.localestá en.gitignore- No hay secretos en el código fuente (busca con
git log -p -S "sk_live") - Solo variables realmente públicas tienen prefijo
NEXT_PUBLIC_ - Variables se validan al arrancar con Zod
- Secretos son distintos entre desarrollo y producción
- Equipo usa
.env.examplecomo referencia, nunca comparte.env.local