Saltar al contenido

import.meta.env: las variables que Astro hereda de Vite

El mecanismo base para leer configuración en Astro viene de Vite: import.meta.env. Por qué el prefijo PUBLIC_ decide qué cruza al navegador y qué se queda en el servidor, qué son las banderas MODE, DEV y PROD que el build inyecta, cómo se cargan y priorizan los archivos .env por modo, y por qué la sustitución es estática en tiempo de compilación y no una lectura dinámica en ejecución.

El prefijo PUBLIC_ traza la frontera cliente/servidor

En Astro, cualquier variable de entorno es legible desde el código de servidor —el frontmatter de una página, un endpoint, el middleware— con import.meta.env.NOMBRE. Pero solo las que empiezan por PUBLIC_ llegan al código que se empaqueta para el navegador. Esta asimetría es deliberada y constituye la primera línea de defensa contra la fuga de secretos: si tu credencial se llama DATABASE_URL, ningún bundle de cliente la contendrá jamás, porque Vite se niega a incrustar en el navegador cualquier nombre que no lleve el prefijo acordado.

TypeScript
// Codigo de servidor: ambas se leen sin problemaconst publica = import.meta.env.PUBLIC_API_URL;   // disponible en cliente y servidorconst privada = import.meta.env.DATABASE_URL;      // solo en el servidor

Lo que hace especial a PUBLIC_ no es un chequeo en tiempo de ejecución, sino una decisión de empaquetado. Cuando Vite construye el bundle del navegador, recorre el código en busca de accesos literales a import.meta.env.PUBLIC_... y los reemplaza por su valor como una cadena. Las variables sin prefijo no existen en ese universo: leer import.meta.env.DATABASE_URL dentro de un <script> de cliente devuelve undefined, no porque algo la oculte en el último momento, sino porque nunca se incrustó en primer lugar.

Las banderas que describen el build: MODE, DEV, PROD

Además de tus variables, Vite inyecta un conjunto de banderas de solo lectura que describen bajo qué condiciones se compila el proyecto. No provienen de ningún archivo .env: las rellena el propio proceso según el comando que lanzaste.

TypeScript
import.meta.env.MODE;      // 'development' con astro dev, 'production' con astro buildimport.meta.env.DEV;       // true en desarrollo, false en el build de produccionimport.meta.env.PROD;      // false en desarrollo, true en el build de produccionimport.meta.env.BASE_URL;  // la base configurada del sitio (opcion base)import.meta.env.SSR;       // true cuando el codigo corre del lado del servidor

DEV y PROD son booleanos siempre opuestos, y su valor se conoce en el build. Eso permite algo más profundo que una simple condición: como Vite sustituye la bandera por una constante, un bloque envuelto en if (import.meta.env.PROD) que no se cumple queda como código muerto y el minificador lo elimina del bundle. Escribes una guarda legible y obtienes, gratis, poda de código según el destino.

El modo no está limitado a esos dos valores. Con astro build --mode staging fuerzas MODE a staging, lo que además cambia qué archivos .env se cargan. Esta es la palanca que separa entornos sin tocar el código: el mismo build, alimentado por distintos archivos, produce artefactos configurados para destinos distintos.

Astro suma sus propias variables al conjunto

A las banderas de Vite, Astro añade variables derivadas de tu configuración, también accesibles por import.meta.env. Describen la identidad y la ubicación del sitio, y te ahorran reimportar la configuración solo para consultarlas.

TypeScript
import.meta.env.SITE;          // la URL declarada en la opcion site de la configimport.meta.env.BASE_URL;      // la subruta base si sirves bajo un prefijoimport.meta.env.ASSETS_PREFIX; // el prefijo de assets cuando usas un CDN aparte

Estas no se definen en un .env: nacen de astro.config.mjs, son de solo lectura y su valor queda fijado en el build, igual que las banderas. Sirven para construir URLs absolutas, resolver rutas de assets o generar enlaces canónicos sin cablear el dominio a mano en cada plantilla.

Hay además una regla de precedencia fácil de olvidar: si una variable ya existe en el entorno real del proceso, ese valor manda sobre lo que digan tus archivos .env. Vite no pisa lo que el sistema ya definió. Es justo el comportamiento que quieres en despliegue, donde la plataforma inyecta las variables de verdad y ningún .env que se colara en la imagen debería sobreescribirlas.

Una forma sana de interiorizar todo esto es comprobarlo en el artefacto. Tras un astro build, busca en la carpeta de cliente el valor de una variable PUBLIC_ y lo hallarás incrustado como texto; busca el de una privada y no aparecerá por ningún lado. Esa inspección convierte la frontera de la que hablamos en un hecho que ves con tus ojos, y es el hábito que más rápido calma la ansiedad de no estar seguro de qué acabó expuesto. Con esa base firme, la siguiente lección da el salto natural: dejar de confiar en un prefijo por convención y declarar cada variable en un esquema que el compilador valida.

Archivos .env y su orden de prioridad

Vite carga automáticamente los archivos .env de la raíz del proyecto y expone su contenido a través de import.meta.env. No hay una sola convención, sino una jerarquía pensada para separar lo compartido de lo secreto y lo genérico de lo específico de cada modo.

Shell
.env                # cargado siempre, en cualquier modo.env.local          # cargado siempre, pero ignorado por git.env.[mode]         # cargado solo en ese modo (development, production, staging).env.[mode].local   # cargado solo en ese modo, ignorado por git

Cuando una misma clave aparece en varios archivos, gana el más específico y el más local. La prioridad, de mayor a menor, es .env.[mode].local, luego .env.[mode], luego .env.local y por último .env. La regla operativa se resume en dos ideas: los .local guardan lo que no se comparte y por eso viven fuera del control de versiones, y los archivos por modo sobreescriben a los genéricos para adaptar valores a cada entorno.

Shell
# .env  -> valor por defecto compartido por todo el equipoPUBLIC_API_URL=https://api.ejemplo.com# .env.development  -> apunta al backend local durante el desarrolloPUBLIC_API_URL=http://localhost:8787# .env.local  -> secretos que solo existen en tu maquinaDATABASE_URL=postgres://usuario:clave@localhost/db

Conviene cerrar con una advertencia operativa que enlaza con las buenas prácticas. Como Vite carga los .env de la raíz del proyecto, cualquier valor que escribas ahí acaba influyendo en el build, y por eso los archivos con secretos reales deben quedar fuera del control de versiones desde el primer día, no cuando ya hay algo delicado dentro. Un secreto que se sube una vez hay que darlo por comprometido para siempre, así que la prevención barata —ignorarlo antes de escribirlo— vale mil veces más que cualquier limpieza posterior.