Saltar al contenido

Leer variables: astro:env/client, /server y getSecret

Cómo se consume el esquema de astro:env desde el código: los módulos virtuales astro:env/client y astro:env/server, la importación por nombre ya tipada, la función getSecret para leer secretos en runtime cuando su valor solo existe en la plataforma, y cuándo valida Astro cada variable, en el build para las públicas y en ejecución para los secretos salvo que actives validateSecrets.

Dos módulos, una frontera respetada

Una vez definido el esquema, cada variable se importa por su nombre desde el módulo que le corresponde. Las variables de context: 'client' viven en astro:env/client; las de context: 'server' —públicas y secretas— viven en astro:env/server. La importación no te da texto crudo: te da el valor ya convertido al tipo que declaraste.

TypeScript
// Cliente: solo ve las variables context clientimport { PUBLIC_API_URL } from 'astro:env/client';// Servidor: ve las variables server, publicas y secretasimport { PORT, API_SECRET } from 'astro:env/server';

Repara en lo que desaparece respecto a import.meta.env. No hay conversión manual: PORT llega como número porque el esquema lo declaró number. No hay nombres mágicos escritos como cadenas: importas identificadores reales que el editor autocompleta y renombra. Y no hay duda sobre la presencia: si una variable requerida falta, el fallo salta antes, no en la línea donde intentas usarla.

getSecret: leer lo que solo existe en runtime

La importación directa de un secreto asume que su valor puede conocerse cuando Astro construye el módulo. Pero muchos secretos no cumplen eso: la clave de una pasarela de pago no está en tu máquina ni en el build, solo aparece inyectada en el entorno del servidor de despliegue, en el instante en que atiende una petición. Para ese caso existe getSecret, una función que también se importa de astro:env/server y lee el valor por su nombre en tiempo de ejecución.

TypeScript
import { getSecret } from 'astro:env/server';export const GET = () => {  const clave = getSecret('PAYMENT_KEY'); // string | undefined, sin validar  if (!clave) return new Response('config incompleta', { status: 500 });  // usar la clave para firmar o autenticar...};

La diferencia con la importación por nombre es profunda. El identificador importado es un valor validado y tipado que el esquema garantiza; getSecret es una lectura dinámica y en crudo que devuelve una cadena o undefined, sin pasar por la validación del esquema. Por eso getSecret sirve para dos cosas: leer un secreto que solo aparece en runtime, y leer una variable que ni siquiera está en el esquema. Es la vía de escape deliberada para lo que no se puede conocer de antemano, con el coste justo de que tú te encargas de comprobar que llegó.

Un uso frecuente de getSecret que conviene destacar es leer variables que ni siquiera están en el esquema. A veces una biblioteca de terceros espera encontrar su clave en una variable con un nombre fijo que tú no declaraste; getSecret te deja alcanzarla sin forzarla dentro de tu contrato, reconociendo que no toda variable del entorno es tuya ni tiene por qué pasar por tu validación.

Cuándo se valida cada variable

El esquema promete validar, pero no todo se valida en el mismo momento, y la razón es puramente física: no puedes comprobar en el build un valor que aún no existe. Astro reparte la validación según la naturaleza de cada variable.

Las variables públicas —de cliente o de servidor— se validan en el build, porque su valor está presente cuando compilas y no hay motivo para posponer el chequeo. Los secretos, en cambio, se validan por defecto en tiempo de ejecución, cuando el módulo se carga en el servidor, precisamente porque su valor puede no existir todavía durante el build. Si sabes que tus secretos sí están disponibles al compilar y prefieres que su ausencia rompa el build en lugar de la primera petición, actívalo explícitamente.

TypeScript
// astro.config.mjsexport default defineConfig({  env: {    schema: { /* ... */ },    validateSecrets: true, // valida tambien los secretos en el build  },});

Errores tempranos y tipos generados

El mayor beneficio diario del esquema no es la lectura tipada, sino lo que ocurre cuando algo va mal. Si una variable requerida falta, Astro no te deja avanzar con un undefined que reventará tres capas más adentro: detiene el arranque con un mensaje que nombra la variable ausente y el módulo que la esperaba. El fallo aparece donde se puede arreglar —la configuración del entorno— y no en la línea remota donde por fin se usaba.

Shell
# Fallo temprano al arrancar sin una variable requerida por el esquema[astro:env] API_SECRET es requerida por el esquema pero no esta definida

Ese diagnóstico depende de que el esquema sea la única fuente de verdad. Como Astro conoce el nombre, el tipo y la obligatoriedad de cada variable, los comprueba todos de una vez al inicio y te ofrece, además, autocompletado real: al escribir el import desde astro:env/server, el editor propone exactamente los nombres declarados y ninguno inventado. Los tipos no los escribes tú; los genera Astro a partir del esquema y los mantiene sincronizados con él, de modo que renombrar una variable en la configuración se propaga a cada uso.

Merece la pena insistir en un punto práctico antes de la reflexión final: la elección del módulo la dicta el esquema, no tú. No decides en el import si una variable es de cliente o de servidor; esa decisión ya la tomaste al declararla, y el módulo del que puedes importarla es su consecuencia directa. Si alguna vez te sorprende no encontrar un nombre donde lo buscas, la respuesta no está en el import sino en el esquema: vuelve a él y revisa qué context le diste, porque ahí se decidió a qué lado de la frontera vive esa variable para siempre.