Saltar al contenido

astro:env: un esquema tipado para tus variables

La evolución de Astro sobre import.meta.env: declarar las variables de entorno en un esquema con envField. Cómo se define en astro.config.mjs, qué significan los dos ejes context cliente o servidor y access público o secreto, por qué la esquina cliente más secreto es imposible por diseño, y cómo los tipos string, number, boolean y enum convierten el texto crudo en valores validados.

Un esquema declara tus variables una sola vez

En lugar de esparcir accesos a import.meta.env por todo el proyecto y esperar que las variables existan, con astro:env declaras la lista completa en la configuración. Cada entrada asigna un nombre a una descripción construida con envField, que se importa junto a defineConfig.

TypeScript
// astro.config.mjsimport { defineConfig, envField } from 'astro/config';export default defineConfig({  env: {    schema: {      PUBLIC_API_URL: envField.string({ context: 'client', access: 'public' }),      PORT: envField.number({ context: 'server', access: 'public', default: 8787 }),      API_SECRET: envField.string({ context: 'server', access: 'secret' }),    },  },});

Esta declaración es la única fuente de verdad. De ella Astro deriva dos cosas a la vez: el conocimiento para validar tu entorno —comprobar que PORT es un número y que API_SECRET está presente— y los tipos que verás al leer cada variable. Ya no hay nombres mágicos dispersos por el código; hay un catálogo central que el compilador conoce y hace respetar.

Los dos ejes: context y access

Cada campo del esquema se sitúa en dos ejes independientes, y ahí reside toda la potencia del sistema. El eje context responde a dónde se puede leer la variable: client significa que su valor viajará al navegador; server que solo existe del lado del servidor. El eje access responde a qué naturaleza tiene: public es un valor que no importa revelar; secret es una credencial que debe permanecer oculta.

TypeScript
envField.string({ context: 'client', access: 'public' }); // llega al navegador, inlineadaenvField.string({ context: 'server', access: 'public' }); // solo servidor, conocida en buildenvField.string({ context: 'server', access: 'secret' }); // solo servidor, leida en runtime

Las tres combinaciones anteriores son legítimas y cubren casos reales distintos. Una URL de API pública se declara client y public: se incrusta en el bundle como haría una variable PUBLIC_. Un puerto de escucha se declara server y public: no es secreto, pero tampoco tiene sentido en el navegador. Una clave de firma se declara server y secret: solo el servidor la ve y, además, se lee en el momento de la petición y no se hornea en ningún artefacto.

Tipos y validadores de envField

envField no solo ubica la variable en los dos ejes: también le asigna un tipo, y con él una conversión y una validación. El texto crudo del entorno se transforma en el valor que tu código espera, y si no encaja, el fallo aparece pronto y con un mensaje claro en vez de propagarse como un NaN silencioso.

Cada tipo acepta además tres opciones transversales que afinan el contrato. Con optional: true marcas que la variable puede faltar, y su ausencia deja de ser un error. Con default das un valor de reserva que se usa cuando el entorno no la aporta, lo que a menudo hace innecesario el optional. Y los validadores propios de cada tipo —un max en una cadena, un rango en un número, un enum cerrado— convierten reglas que antes vivían en comentarios o en tu memoria en condiciones que la máquina comprueba por ti.

TypeScript
env: {  schema: {    LOG_LEVEL: envField.enum({      context: 'server', access: 'public',      values: ['debug', 'info', 'warn', 'error'], default: 'info',    }),    MAX_UPLOAD_MB: envField.number({ context: 'server', access: 'public', default: 10 }),  },}

Los validadores no son un adorno: cada regla que mueves del comentario al esquema es una comprobación que la máquina hace por ti en cada arranque. Un enum cerrado evita el error tipográfico que dejaría tu nivel de log en un valor que ninguna rama contempla; un rango numérico atrapa el puerto imposible antes de que el servidor intente escuchar en él. La validación deja de ser un acto de fe y pasa a ser una propiedad del sistema.

Qué cambia respecto a import.meta.env

Conviene situar astro:env frente al mecanismo de la lección anterior, porque conviven y no siempre es obvio cuándo preferir cada uno. import.meta.env sigue existiendo y es perfecto para las banderas del build —MODE, DEV, PROD— y para leer alguna variable sin ceremonia en un proyecto pequeño. astro:env gana en cuanto el proyecto crece y quieres garantías: tipos reales en vez de cadenas, validación de presencia y esa frontera cliente/servidor convertida en algo que el compilador impone.

TypeScript
// Antes: cadena sin garantias, conversion y comprobacion a manoconst puerto = Number(import.meta.env.PORT ?? 8787);// Ahora: numero validado por el esquema, sin conversion ni valor magico dispersoimport { PORT } from 'astro:env/server';

La diferencia no es de comodidad, sino de dónde recae la responsabilidad. Con import.meta.env, cada lugar que lee una variable debe recordar convertirla y defenderse de su ausencia; el conocimiento sobre su forma vive disperso y repetido, y basta que un solo sitio lo olvide para abrir un agujero. Con el esquema, ese conocimiento se declara una vez y se hace cumplir en todas partes: la variable entra al programa por un único portón vigilado en lugar de por mil puertas traseras que cada quien asegura como puede.