Astro
Astro Vercel Variables, Explained Clearly
Written by Noel
Published:
21 min read
Topics researched with AI assistance; reviewed and edited by Noel before publishing.

Explore this topic
More Astro guides, glossary entries, and practical workflows live on the topic hub.
Astro environment variables are the values your project reads from the environment instead of hard-coding into components, pages, or build scripts. In the context of an Astro Vercel deployment, they are the bridge between your code and the settings that change by environment: local development, preview builds, and production.
Used well, they keep secrets out of the browser, make deployments reproducible, and let you switch APIs or site settings without rewriting code. Used poorly, they create the usual deployment problems: missing values, leaked secrets, and code that behaves differently on Vercel than it did on your laptop.
Key takeaways
- Astro follows Vite’s environment variable model, so
import.meta.envis the main access point in project code.- Only variables prefixed with
PUBLIC_belong in client-side code; everything else should stay server-side.astro.config.mjsis evaluated early, so.envvalues are not always available there the way developers expect.- Vercel deployment settings must match your local variable names and environment modes, or builds will drift.
- Default values like
MODE,DEV,PROD,SITE, andBASE_URLhelp you write environment-aware code without adding custom flags.
What is it?
Astro environment variables are configuration values that your site reads at runtime or build time, depending on where the code runs. The phrase astro vercel adapter environment variables usually points to one practical question: how do you keep the same Astro project working locally and on Vercel without exposing secrets or breaking builds?
The answer is to separate three ideas. First, you have default Astro values such as MODE, DEV, PROD, SITE, and BASE_URL. Second, you have your own custom variables loaded from .env files or hosted environment settings. Third, you have the access boundary: server-side code can see more than client-side code, and only PUBLIC_ variables are meant to cross into the browser.
A concrete example helps. Imagine a storefront built with Astro that needs a public API endpoint for product data and a private token for server-side requests. The public endpoint can live in PUBLIC_API_URL and be read in a component or browser script. The token should stay in API_TOKEN and be used only in server code, such as a page load function, endpoint, or middleware. If you mix those up, the browser may receive something it should never see.
This matters even more when you deploy with Vercel, because the same code can run in multiple environments. A preview deployment might use a staging API, while production uses the live API. If your variable naming and loading strategy are inconsistent, the site may build successfully but point to the wrong backend or fail only after deployment.
A second example is a blog or marketing site that needs a canonical domain, a base path, and a tracking ID. Those values are not secrets, but they still belong in environment-aware configuration because they change by deployment target. SITE and BASE_URL help with the first two, while a public analytics ID can live in a PUBLIC_ variable. The point is not just to avoid hard-coding; it is to keep the deployment contract visible.
Why it matters — business and technical impact
For merchants, the business value is simple: environment variables reduce deployment risk. A storefront or content site often needs different settings for analytics, search, CMS access, or API endpoints. If those values are hard-coded, every change becomes a code change. If they are managed as environment variables, the team can update configuration without touching the application logic.
For developers, the technical value is even broader. Environment variables make the codebase portable across local machines, CI, preview builds, and production. They also help you keep secrets out of client bundles. In Astro, that distinction is not cosmetic; it is part of how the framework protects sensitive values when code is shipped to the browser.
There is also a maintainability angle. Teams often discover that their Astro project behaves differently in astro dev, astro build, and on Vercel because the environment was never modeled explicitly. One page expects a site value, another expects a PUBLIC_ API URL, and the config file expects a variable that only exists in the deployment dashboard. Clear environment management turns those hidden assumptions into visible project settings.
From a deployment perspective, the impact is strongest when you work with multiple modes. Astro can load different .env files for development, staging, testing, or production. That means you can point preview builds to a test API while production uses the real one. The result is less accidental cross-environment traffic and fewer surprises when a release goes live.
It also improves collaboration between technical and non-technical teams. A marketer can update a tracking ID or public feature flag in the deployment dashboard without waiting for a code release, while a developer can keep private credentials out of the repository entirely. That separation reduces the chance that a small operational change turns into a risky deploy.
A useful way to think about the business impact is in terms of change control. When a value is in code, changing it usually requires a pull request, review, build, and deploy. When a value is in the environment, the change can be isolated to the correct deployment target. That does not mean every setting should be editable by everyone; it means the team can choose the right control surface for the right kind of change.
There is a technical reliability benefit too: environment variables help you make failure modes obvious. If a required value is missing, the app should fail early in a predictable place rather than degrade silently in production. That is especially important on Vercel, where preview and production deployments can look similar but still depend on different settings. A missing variable that only affects one environment is much easier to diagnose when the project has a clear naming convention and a documented checklist.
How it works — explain the mechanism step by step
Astro uses Vite’s environment variable system. That means variables are typically read through import.meta.env, not process.env, in project code. Astro also provides default values that describe the current build or runtime context, so your code can tell whether it is running in development, production, or a specific site configuration.
The flow usually looks like this:
- You define variables in
.envfiles for local work or in Vercel’s dashboard for hosted environments. - Astro loads those values according to the current mode, such as
developmentorproduction. - Project code reads them through
import.meta.envwhere supported. - Variables with the
PUBLIC_prefix can be used in client-side code; other variables stay server-side. - During build, Vite can statically replace values, which is why naming and timing matter.
That timing detail is important. Astro evaluates astro.config.mjs before it loads the rest of your project files. So if you expect a .env value to be available there through import.meta.env, you may be disappointed. In config files, you typically rely on process.env or use Vite’s loadEnv helper to read .env values manually.
Another part of the mechanism is mode selection. astro dev defaults to development mode, while astro build defaults to production mode. You can override that with a --mode flag to load a different .env file, which is useful when you want a staging build to point at a staging API. That same idea maps well to Vercel preview deployments, where you often want a separate set of values from production.
Finally, remember the security boundary. In server-side code, all environment variables are available. In client-side code, only PUBLIC_ variables are available. That rule is what keeps secrets safe while still letting you expose non-sensitive configuration such as a public API base URL or feature flag.
A useful mental model is to think of environment variables as being resolved in layers. The shell or platform provides one layer, .env files provide another, Astro defaults provide another, and the code decides which layer it can read from based on where it runs. When those layers are aligned, the app feels predictable; when they are not, the same variable can appear to “work” in one place and disappear in another.
In practice, the build pipeline matters as much as the variable itself. A value may be available when a server route executes, but not when static content is generated at build time. That is why teams should decide early whether a setting is needed during compilation, during server rendering, or in the browser. The same name can be used across those layers only if the access rules are understood first.
A good implementation also accounts for the adapter boundary. When you use the Vercel adapter, Astro still follows the same env rules, but the deployment platform becomes the source of truth for hosted values. That means the adapter does not change the security model; it changes where the values are supplied and how carefully you need to mirror them between local and hosted environments.
Use cases — where teams actually apply this
The most common use case is a content or marketing site that needs different settings by environment. A team may want preview deployments to use a staging CMS, while production uses the live CMS. With environment variables, the code stays the same; only the values change. That makes editorial previews safer and avoids accidental edits against production content.
A second use case is headless commerce. A storefront might call a public catalog API from the browser, but use a private token on the server for secure requests, cache revalidation, or protected endpoints. In that setup, the PUBLIC_ prefix is not just a naming convention. It is a guardrail that keeps the browser from receiving secrets while still allowing the storefront to render dynamic data.
A third use case is deployment-specific behavior. Teams often need to know whether a build is running in production, whether a site is served from a subpath, or what canonical domain should be used in metadata. Astro’s default variables such as SITE, BASE_URL, MODE, DEV, and PROD help with that. They are especially useful when a site is deployed to Vercel with multiple environments and the same codebase must behave consistently across all of them.
There is also a developer-experience use case: type safety. When a project grows, environment variables become easy to mistype. Astro supports type-safe environment variables through schema-based configuration and TypeScript declarations. That reduces the chance of shipping code that reads PUBLIC_API_URl instead of PUBLIC_API_URL, which is the kind of bug that can hide until deployment.
Another practical scenario is feature rollout. A team can keep a new integration disabled in production while enabling it in preview or staging with a single flag. That lets designers, editors, or QA review the behavior before it reaches customers. The same pattern works for analytics, A/B testing, and temporary maintenance banners, as long as the variable is named and scoped carefully.
A useful decision rule is this: use environment variables when the value changes by deployment target, by secret boundary, or by build mode. Avoid them when the value is really content, user input, or a setting that should be versioned with the rest of the application logic. That distinction keeps your deployment settings from becoming a second, undocumented configuration system.
In day-to-day work, teams also use these variables to keep local debugging honest. If a developer can point a local build at the same kind of backend that preview uses, they can reproduce problems before they reach Vercel. That shortens the feedback loop and makes it easier to tell whether a bug is caused by code, data, or configuration.
How to implement or apply it — practical guidance
Start by deciding which values are public and which are private. If a value must be visible in browser code, prefix it with PUBLIC_. If it is a secret, token, password, or internal endpoint, keep it unprefixed and use it only in server-side code. This decision should happen before you write the first line of implementation, because it affects where the variable can be read.
Next, define your local environment files. A typical project might use .env for shared defaults, .env.development for local development, and .env.production or a custom mode file for other environments. The point is not to create many files for their own sake; it is to make the environment explicit. If you run a staging build, the staging values should be obvious from the filename and the mode.
For code that runs in Astro components, routes, endpoints, middleware, or UI framework components, read values from import.meta.env. For config files, be more careful. Since astro.config.mjs loads early, use process.env for variables already present in the shell, or load .env files manually with Vite’s helper. That distinction saves time when a config value appears to “disappear” only in build contexts.
If you deploy on Vercel, mirror the same variable names in the project settings. The values can differ by environment, but the names should not. That consistency is what keeps local development, preview deployments, and production aligned. When a project has a public site URL, a private API token, and a mode-specific backend URL, each one should be named once and reused everywhere.
A practical workflow looks like this:
- define the variable name first
- decide whether it is public or server-only
- add it to local
.envfiles - add the same name to Vercel environment settings
- read it in the correct execution context
- verify the value in development, preview, and production
If you need to reason about page rendering or server/client boundaries, it can help to pair this with a broader understanding of Astro Islands architecture so you know where browser code starts and server code ends.
When you are implementing a new variable, test the smallest possible path first. For example, log or render the value in a server-only route before wiring it into a layout or component tree. That makes it easier to tell whether the problem is the variable itself, the environment it was loaded from, or the place you are trying to read it. Once the value is confirmed, move it into the real flow and remove any temporary debugging output.
If the variable affects URLs, metadata, or asset paths, validate the output in both preview and production. A site can look correct in development while still generating the wrong canonical domain or base path after deployment. That is where Astro defaults like SITE and BASE_URL are especially helpful, because they reduce the need for custom logic and make the intended deployment shape easier to inspect.
For teams using TypeScript, add explicit env typings early. Even a small env.d.ts file can prevent accidental typos and make editor autocomplete more reliable. That is especially helpful when a project has several public variables, because the compiler can catch mistakes before they become deployment issues.
Common mistakes and pitfalls
The most common mistake is exposing a secret by accident. If a variable is meant for server use only, it should not be prefixed with PUBLIC_, and it should never be read from client-side code. This mistake is easy to make when a developer copies a variable name from one environment to another without checking where it will run.
A second mistake is assuming import.meta.env works everywhere. It works in project code, but not always in astro.config.mjs the way people expect. Because the config file is evaluated early, .env values may not be loaded yet. When that happens, developers sometimes think the variable is missing from Vercel, when the real issue is timing inside the build process.
A third pitfall is inconsistent naming between local and hosted environments. A local .env file might use PUBLIC_API_URL, while Vercel uses API_URL or PUBLIC_APIURL. The code then works in one environment and fails in another. This is especially painful when the failure only appears in preview builds or after a production deployment.
Another issue is overusing environment variables for values that should live in content or configuration data. If a setting changes often but is not sensitive, it may belong in content collections or a CMS rather than in deployment settings. Environment variables are best for environment-specific configuration, not for every piece of mutable content.
Finally, teams sometimes forget that default Astro variables already solve part of the problem. If you need to know whether you are in development or production, MODE, DEV, and PROD already exist. If you need the configured site URL or base path, SITE and BASE_URL are already there. Reusing those defaults is cleaner than inventing your own duplicate flags.
A related pitfall is assuming a value should be available in every build target. In practice, server-rendered pages, static pages, endpoints, and browser scripts do not all have the same access rules. If a variable is needed in multiple places, you may need two versions of the same concept: one server-only secret and one public derived value. That is safer than trying to force a single variable into every layer.
One more subtle mistake is forgetting to document the intended environment for each variable. A name like PUBLIC_API_URL tells you the access boundary, but not whether it should point to staging or production in a given deployment. Without a short note in the repo or deployment checklist, teams can accidentally swap values during a release and spend time tracing a problem that was really just a configuration mismatch.
A final pitfall is treating a successful build as proof that the environment is correct. A build can pass with the wrong value if the variable is optional or if the wrong environment target is selected in Vercel. The safer habit is to verify the rendered output, the network target, and the deployment environment together, especially when a variable controls a backend URL or a public integration key.
Best practices and quick checklist
The safest pattern is to treat environment variables as part of your deployment design, not as an afterthought. Decide early which values are public, which are private, and which are already covered by Astro defaults. That makes the code easier to review and the deployment easier to reproduce.
Keep the names stable across environments. If the same app runs on local, preview, and production, use the same variable names everywhere and change only the values. That consistency reduces branching logic in code and makes it easier to debug a failed build.
Use the right access point for the job. In project code, read variables through import.meta.env. In config files, use process.env or loadEnv when needed. In client code, only rely on PUBLIC_ values. In server code, keep secrets server-side and avoid passing them through the browser unless there is a clear reason.
A quick checklist:
- separate public and private values before implementation
- use
PUBLIC_only for values safe to expose - mirror local names in Vercel settings
- verify config-file access separately from page code
- prefer Astro defaults like
MODE,SITE, andBASE_URLwhen they fit - test development, preview, and production with the same variable names
- document each variable’s purpose for the team
- confirm whether the value is needed at build time or only at request time
If your project includes structured content or dynamic page generation, it is also worth revisiting Astro content collections so you do not put content-like data into environment variables by habit.
A good review habit is to ask three questions before merging: does this value need to be public, does it need to vary by environment, and is Astro already giving me a default for it? If the answer to all three is no, the variable may not need to exist at all. That kind of pruning keeps deployment settings understandable as the project grows.
For teams working with Vercel, it also helps to keep a short environment matrix in the repo: which variables exist in development, preview, and production, and which ones are required for a successful build. That matrix does not need to be elaborate. Even a simple table in the README can prevent the most common “works locally, fails on deploy” problem by making the expected configuration visible to everyone on the team.
A final best practice is to treat the environment as part of your release checklist. Before shipping, confirm the deployment target, the variable names, the public/private split, and the values that affect URLs or integrations. That small review step catches the kinds of mistakes that are hardest to see in code review because they only appear once the app is running in a real environment.
From practice — illustrative scenario (hypothetical, not a client project)
Illustrative example — not a real client project: imagine a merchant running an Astro storefront on Vercel with a public product API, a private admin token, and a separate preview environment for the marketing team. The site needs to show product data in the browser, generate metadata on the server, and keep preview builds pointed at a staging backend.
At setup time, the team defines PUBLIC_API_URL for browser-safe requests, ADMIN_API_TOKEN for server-side operations, and a site URL in Vercel for each environment. They also set SITE and BASE_URL through the Astro config so canonical links and asset paths stay correct. In local development, the same names exist in .env.development, while Vercel stores matching names for preview and production.
The workflow starts with a simple decision tree. If the value is needed by browser code, it becomes public and gets the PUBLIC_ prefix. If it is only needed for server-side rendering, API calls, or middleware, it stays private. If it is a built-in site setting such as the canonical domain or base path, the team checks whether Astro already exposes a default before creating a custom variable.
The problem appears when a developer tries to read a staging value in astro.config.mjs using import.meta.env. The variable seems to vanish during build, and the preview deployment points to the wrong backend. Rather than changing the app code, the team moves config-file access to process.env or loads the values explicitly with Vite’s helper. That keeps the config phase separate from page rendering and removes the timing issue.
Next, the team checks whether the variable is needed at build time or only at request time. The public API URL is needed in browser code and server-rendered pages, so it must be present in both local and hosted environments. The admin token is only needed on the server, so it never appears in client code or in any shared UI component. That split keeps the browser bundle clean and makes the security boundary obvious during code review.
Later, the team adds a preview-only feature flag for a new checkout banner. They keep the flag public because the browser needs to read it, but they name it clearly so it is obvious that it is not a secret. They also document the flag in the repo so editors and developers know which environment controls it. That small bit of discipline prevents the flag from being mistaken for a private credential during a future deploy.
The team then runs a short verification pass in three places: local development, Vercel preview, and production. In each environment they confirm the same variable names exist, the public values resolve in the browser, and the private values remain server-only. They do not change the code between environments; they only change the values. That is the real payoff of a good environment strategy.
The takeaway is not that environment variables are hard. The takeaway is that they work best when each layer has a clear job. Browser code gets public values only. Server code gets the private values it needs. Config files read from the shell or load .env deliberately. Once those boundaries are clear, the same Astro project can move between local development, preview builds, and production without changing the application logic.
A second useful lesson from this scenario is that the team does not try to solve every problem with a single variable. The public API URL, the private admin token, and the site metadata each serve different purposes and therefore belong to different scopes. That separation makes the deployment safer and the debugging process faster, because each value has one obvious place to look when something goes wrong.
Related concepts and further reading
If you are tightening up an Astro deployment, these related guides help with the surrounding pieces: how content is structured, how rendering boundaries affect configuration, and how deployment settings interact with performance.
- Astro content collections guide — useful when content belongs in collections instead of env vars
- Astro islands architecture — helps you decide what runs on the server versus in the browser
- Astro Technical SEO for Growth — useful when environment values affect canonical URLs, metadata, or indexing
- Astro documentation — official reference for environment variable behavior
Explore this topic
More Astro guides, glossary entries, and practical workflows live on the topic hub.
Frequently asked questions
What is the difference between PUBLIC_ and server-only environment variables in Astro?
In Astro, variables prefixed with PUBLIC_ can be exposed to client-side code, while server-only variables stay available only on the server. That separation matters because anything sent to the browser should be treated as public. If a value is sensitive, keep it server-side and never read it in client code.
Can I use import.meta.env in astro.config.mjs?
Not for values loaded from .env files in the usual way. Astro evaluates the config file before it loads the rest of the project, so you typically use process.env there or load variables manually with Vite’s loadEnv helper. This is a common source of confusion during deployment setup.
Why do environment variables work locally but fail on Vercel?
Local development and hosted builds do not always share the same environment settings. On Vercel, you need to define variables in the project settings for the correct environment, such as development, preview, or production. If a variable is missing or named differently, the build may succeed locally but fail in deployment.
How does Astro decide whether an environment variable is available in the browser?
Astro follows Vite’s environment variable rules. Variables with the PUBLIC_ prefix are available in client-side code, while other variables are available only in server-side code. That means the prefix is part of both the access pattern and the security model.
Should I use .env files or Vercel dashboard variables?
Use .env files for local development and the Vercel dashboard for hosted environments. The key is consistency: the same variable names should exist in both places, even if the values differ by environment. That makes builds easier to reproduce and reduces surprises when you deploy.
Do default Astro variables like MODE and SITE matter for deployment?
Yes, because they help you distinguish development from production and read project-level settings like site and base. They are useful for conditional logic, canonical URLs, and debugging environment-specific behavior. They do not replace your own custom variables, but they often belong in the same implementation plan.