How to Configure Build-Time vs. Runtime Environment Variables in Astro Projects

Astro distinguishes build-time from runtime environment variables using the PUBLIC_ prefix, where variables without the prefix are inlined during the build for server-only use, while PUBLIC_ prefixed variables remain accessible to the browser and resolve at request time.

Astro projects rely on environment variables to manage configuration across different deployment stages. In the withastro/astro repository, the framework implements a strict separation between build-time secrets and runtime public values to ensure sensitive data never leaks to the client while allowing dynamic configuration for browser-accessible settings.

Understanding the Two Variable Types

Astro categorizes environment variables based on when their values are fixed and who can access them:

Stage Definition How to Access When the Value is Fixed
Build-time Variables that do not start with PUBLIC_ import.meta.env.VARIABLE_NAME At build time – the value is baked into the static output and cannot change at runtime.
Runtime Variables prefixed with PUBLIC_ (e.g., PUBLIC_API_URL) import.meta.env.PUBLIC_VARIABLE At runtime – the value is resolved on each request or page load and can change without rebuilding.

According to the source code in packages/astro/src/runtime/server/environment.ts, Astro builds the import.meta.env proxy by filtering out non-public variables and resolving values from process.env at the appropriate stage.

Configuring Build-Time (Server-Only) Variables

Build-time variables are inlined during the compilation process. Astro substitutes the reference with a literal string in the compiled code, making the value unavailable to the browser and unchangeable after deployment.

Create a .env file in your project root without the PUBLIC_ prefix:

// .env
API_SECRET=mySuperSecretKey

Access the variable in server-only code using import.meta.env:

// src/pages/api/secret.ts (server-only route)
export async function get() {
  const secret = import.meta.env.API_SECRET; // value is replaced at build time
  return new Response(`The secret is ${secret}`);
}

The value mySuperSecretKey is baked into the built server bundle and never sent to the browser.

Configuring Runtime (Public) Variables

Runtime variables are resolved when the code executes rather than when it compiles. Prefix the variable name with PUBLIC_ to expose it to client-side code:

// .env
PUBLIC_API_URL=https://api.example.com

Use import.meta.env.PUBLIC_VARIABLE in components or pages:

// src/components/Fetcher.tsx
export default function Fetcher() {
  // Resolved at runtime, can differ between deployments
  const apiUrl = import.meta.env.PUBLIC_API_URL;

  const [data, setData] = useState(null);
  useEffect(() => {
    fetch(`${apiUrl}/data`).then(r => r.json()).then(setData);
  }, [apiUrl]);

  return <pre>{JSON.stringify(data, null, 2)}</pre>;
}

Because the variable is prefixed with PUBLIC_, Astro injects it into the HTML so the browser can read it. In a server-side context, Astro reads the actual environment at request time; in a client-side context, the value is injected into the compiled output for the browser to access.

Using Mixed Variables in Server-Side Rendering

You can combine both variable types in .astro files. The server resolves build-time variables during the build process and runtime variables on each request:

---
// src/pages/index.astro
const buildTime = import.meta.env.SITE_TITLE; // baked at build
const runtimeApi = import.meta.env.PUBLIC_API_URL; // resolved on each request
---
<html>
  <head><title>{buildTime}</title></head>
  <body>
    <h1>{buildTime}</h1>
    <p>Fetching from {runtimeApi}...</p>
  </body>
</html>

The SITE_TITLE value is embedded as a static string during compilation, while PUBLIC_API_URL is read from the environment when the server handles the request.

Summary

  • Use the PUBLIC_ prefix only when you need the variable available in the browser; otherwise, it stays server-only.
  • Build-time variables are inlined into the compiled code and cannot change without rebuilding the site.
  • Runtime variables resolve on each request, allowing you to update configuration by changing the server environment without triggering a new build.
  • The core logic implementing this behavior resides in packages/astro/src/runtime/server/environment.ts.

Frequently Asked Questions

What is the difference between build-time and runtime environment variables in Astro?

Build-time variables are defined without the PUBLIC_ prefix and are inlined as literal strings during the build process, making them fixed and server-only. Runtime variables use the PUBLIC_ prefix and are resolved when the code executes, either on the server during request handling or in the browser when the page loads.

How do I keep sensitive API keys secret in Astro?

Store sensitive keys in your .env file without the PUBLIC_ prefix (e.g., API_SECRET_KEY) and access them via import.meta.env.API_SECRET_KEY only in server-side code. Astro's build process inlines these values and never exposes them to client-side bundles or the browser.

Can I change environment variables after building an Astro site?

You can change runtime variables (those prefixed with PUBLIC_) by updating the environment where your server runs, as these are read at request time. However, build-time variables are baked into the static output and cannot be modified without rebuilding the site.

Where does Astro implement the environment variable logic?

The core implementation lives in packages/astro/src/runtime/server/environment.ts, which constructs the import.meta.env proxy, filters out non-public variables for client bundles, and resolves values from process.env. The conventions are documented in docs/en/reference/configuration-reference.md.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →