//
Busca en todas las páginas de la documentación
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥
Configura y usa de forma segura variables de entorno en Next.js 15+ con archivos .env, el prefijo NEXT_PUBLIC_ para exposición del cliente, configuración en tiempo de ejecución, y el patrón server-only para prevenir fuga de secretos.
# .env.local (ignorado por git, anulaciones locales)
DATABASE_URL="postgresql://user:pass@localhost:5432/mydb"
AUTH_SECRET="super-secret-key-never-expose"
# Las variables seguras para el cliente deben usar el prefijo NEXT_PUBLIC_
NEXT_PUBLIC_APP_URL="http://localhost:3000"
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY="pk_test_abc123"# .env (registrado en git, valores predeterminados compartidos)
NEXT_PUBLIC_APP_NAME="My App"# .env.production (anulaciones de producción)
NEXT_PUBLIC_APP_URL="https://myapp.com"// lib/env.ts
import "server-only";
function requireEnv(key: string): string {
const value = process.env[key];
if (!value) {
throw new Error(`Missing required environment variable: ${key}`);
}
return value;
}
export const env = {
DATABASE_URL: requireEnv("DATABASE_URL"),
AUTH_SECRET: requireEnv("AUTH_SECRET"),
} as const;// lib/env-client.ts
export const clientEnv = {
appUrl: process.env.NEXT_PUBLIC_APP_URL ?? "http://localhost:3000",
appName: process.env.NEXT_PUBLIC_APP_NAME ?? "My App",
stripeKey: process.env.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY ?? "",
} as const;// lib/env.ts
import "server-only";
import { z } from "zod";
const envSchema = z.object({
DATABASE_URL: z.string().url(),
AUTH_SECRET: z.string().min(32),
NODE_ENV: z.enum(["development", "production", "test"]),
});
export const env = envSchema.parse(process.env);
// lib/env-client.ts
const clientEnvSchema = z.object({
NEXT_PUBLIC_APP_URL: z.string().url(),
NEXT_PUBLIC_APP_NAME: z.string(),
});
export const clientEnv = clientEnvSchema.parse({
NEXT_PUBLIC_APP_URL: process.env.NEXT_PUBLIC_APP_URL,
NEXT_PUBLIC_APP_NAME: process.env.NEXT_PUBLIC_APP_NAME,
});// app/dashboard/page.tsx
import { env } from "@/lib/env";
export default async function DashboardPage() {
// Seguro: esto se ejecuta solo en el servidor
const data = await fetch(`${env.DATABASE_URL}/api/data`);
return <div>{/* renderizar datos */}</div>;
}.env automáticamente en este orden de prioridad (el más alto gana): .env.$(NODE_ENV).local > .env.local > .env.$(NODE_ENV) > .env.NEXT_PUBLIC_ se integran en línea en el bundle del cliente en tiempo de compilación. Todas las otras referencias a process.env se reemplazan con undefined en el código del cliente.process.env no es un objeto real en el código del cliente. Next.js realiza reemplazo de cadena estática en tiempo de compilación. El acceso dinámico como process.env[key] no funcionará en componentes del cliente.server-only causa un error en tiempo de compilación si un módulo se importa desde un Componente del Cliente, proporcionando una garantía firme de que los secretos permanecen en el servidor.serverRuntimeConfig / publicRuntimeConfig en next.config.js (patrón heredado de Pages Router).Variables de Entorno en Tiempo de Ejecución (Docker):
// next.config.ts
import type { NextConfig } from "next";
const nextConfig: NextConfig = {
// Expone env del lado del servidor en tiempo de ejecución (no en tiempo de compilación)
serverRuntimeConfig: {
databaseUrl: process.env.DATABASE_URL,
},
// Expone al servidor y cliente en tiempo de ejecución
publicRuntimeConfig: {
apiUrl: process.env.NEXT_PUBLIC_API_URL,
},
};
export default nextConfig;Usar T3 Env para Validación Completa:
// env.mjs
import { createEnv } from "@t3-oss/env-nextjs";
import { z } from "zod";
export const env = createEnv({
server: {
DATABASE_URL: z.string().url(),
AUTH_SECRET: z.string().min(1),
},
client: {
NEXT_PUBLIC_APP_URL: z.string().url(),
},
runtimeEnv: {
DATABASE_URL: process.env.DATABASE_URL,
AUTH_SECRET: process.env.AUTH_SECRET,
NEXT_PUBLIC_APP_URL: process.env.NEXT_PUBLIC_APP_URL,
},
});env.d.ts para aumentar ProcessEnv para autocompletado:// env.d.ts
declare namespace NodeJS {
interface ProcessEnv {
DATABASE_URL: string;
AUTH_SECRET: string;
NEXT_PUBLIC_APP_URL: string;
NEXT_PUBLIC_APP_NAME: string;
}
}process.env[varName] siempre será undefined en el cliente porque Next.js realiza reemplazo de cadena estática, no búsqueda en tiempo de ejecución..env.local no se carga en entornos de test por defecto. Usa un archivo .env.test.local o carga env manualmente en tu configuración de pruebas..env requiere reiniciar el servidor de desarrollo. La recarga en caliente no detecta cambios en variables de entorno.NEXT_PUBLIC_ son visibles en el bundle del navegador. Nunca pongas secretos (claves de API, URLs de base de datos, secretos de autenticación) detrás de este prefijo.standalone y establece variables de entorno en el contenedor en ejecución, no en el paso RUN del Dockerfile.| Enfoque | Ventajas | Desventajas |
|---|---|---|
Archivos .env con NEXT_PUBLIC_ | Integrado, cero configuración | Sin validación, sin env en tiempo de ejecución |
Validación Zod en lib/env.ts | Seguridad de tipo, falla rápida | Configuración manual |
T3 Env (@t3-oss/env-nextjs) | Validación completa, división cliente/servidor | Dependencia adicional |
next.config.js runtimeConfig | Env verdadero en tiempo de ejecución | Patrón heredado, no nativo de App Router |
| Platform env (Vercel, AWS) | Seguro, por entorno | Configuración específica del proveedor |
process.env.NEXT_PUBLIC_APP_URL se reemplazan.process.env[key]) no se puede resolver en tiempo de compilación y retorna undefined..env.$(NODE_ENV).local (prioridad más alta).env.local.env.$(NODE_ENV).env (prioridad más baja).env.local está ignorado por git por defecto.NEXT_PUBLIC_..env.next dev para que los cambios surtan efecto.server-only causa un error en tiempo de compilación si un Componente del Cliente importa el módulo.NEXT_PUBLIC_ siempre se integran en tiempo de compilación y no pueden cambiar en tiempo de ejecución.docker run -e KEY=value).output: "standalone" para que process.env lea suceda en tiempo de ejecución para código de servidor..env.local cuando NODE_ENV=test por defecto..env.test.local en su lugar, o carga manualmente archivos de entorno en tu configuración de pruebas.// env.d.ts
declare namespace NodeJS {
interface ProcessEnv {
DATABASE_URL: string;
AUTH_SECRET: string;
NEXT_PUBLIC_APP_URL: string;
}
}ProcessEnv para que process.env.DATABASE_URL reciba autocompletado..url(), .min(32)) no solo la presencia.NEXT_PUBLIC_ se reemplazan estáticamente en tiempo de next build.NEXT_PUBLIC_ cambia.@t3-oss/env-nextjs) proporciona una única llamada createEnv que separa esquemas de servidor y cliente.NEXT_PUBLIC_ estén en la sección client y las variables de servidor estén en server.import { z } from "zod";
const envSchema = z.object({
DATABASE_URL: z.string().url(),
AUTH_SECRET: z.string().min(32),
});
// El tipo se infiere automáticamente:
// { DATABASE_URL: string; AUTH_SECRET: string }
export const env = envSchema.parse(process.env);AUTH_SECRETRevisado por Chris St. John·Última actualización: 7 jul 2026
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥