withastro/astro · error · AstroError

ServerOnlyModule

ServerOnlyModule

Error message

The "astro:env/server" module is only available server-side.

What it means

The virtual module `astro:env/server` resolves server-only validated environment variables and must never ship to the browser. When Vite's plugin handler is asked to load it from inside the Astro client environment (`isAstroClientEnvironment`), Astro throws `ServerOnlyModule` to prevent server secrets from being bundled into client code.

Source

Thrown at packages/astro/src/env/vite-plugin-env.ts:86

				}
			},
		},
		load: {
			filter: {
				id: new RegExp(
					`^(${RESOLVED_CLIENT_VIRTUAL_MODULE_ID}|${RESOLVED_SERVER_VIRTUAL_MODULE_ID}|${RESOLVED_INTERNAL_VIRTUAL_MODULE_ID})$`,
				),
			},
			handler(id) {
				if (id === RESOLVED_INTERNAL_VIRTUAL_MODULE_ID) {
					return { code: `export const schema = ${JSON.stringify(schema)};` };
				}

				if (
					id === RESOLVED_SERVER_VIRTUAL_MODULE_ID &&
					isAstroClientEnvironment(this.environment)
				) {
					throw new AstroError({
						...AstroErrorData.ServerOnlyModule,
						message: AstroErrorData.ServerOnlyModule.message(SERVER_VIRTUAL_MODULE_ID),
					});
				}

				if (id === RESOLVED_CLIENT_VIRTUAL_MODULE_ID || id === RESOLVED_SERVER_VIRTUAL_MODULE_ID) {
					const loadedEnv = envLoader.get();

					const validatedVariables = validatePublicVariables({
						schema,
						loadedEnv,
						validateSecrets,
						sync,
					});
					const { client, server } = getTemplates({
						schema,
						validatedVariables,
						// In dev, we inline process.env to avoid freezing it

View on GitHub (pinned to d081033d5f)

Solutions

  1. Remove the `astro:env/server` import from any module reachable by client-side code; keep server env usage in server-only entrypoints (endpoints, middleware, server islands).
  2. Split shared utilities so server-only imports live in a separate file the browser never imports.
  3. If the value is needed client-side, declare it in `env.schema` with `access: 'public'` and import it from `astro:env/client` instead.
  4. Trace the import chain (Vite's error or `vite build --debug`) to find which client entrypoint pulls in the server module.

Example fix

// before — shared util imported by both server and client
import { DATABASE_URL } from 'astro:env/server';
export function dbUrl() { return DATABASE_URL; }

// after — server util is server-only; client gets nothing from this path
// server.ts (never imported by client code)
import { DATABASE_URL } from 'astro:env/server';
export function dbUrl() { return DATABASE_URL; }
Defensive patterns

Strategy: validation

Validate before calling

import { isServer } from 'astro';
// Gate server-env imports behind a server check, or better: keep them in server-only files.
// Static-analysis alternative: ensure 'astro:env/server' only appears in files under /server or endpoints.

Type guard

null

Try / catch

null

Prevention

When it happens

Trigger: Importing `astro:env/server` (directly or transitively) in code that Vite resolves under the Astro client environment — e.g. a component rendered on the client, a `<script>` block, or a client directive entrypoint that pulls in server env values.

Common situations: Accidentally importing the server env module in a shared utility that is also imported by client-side code; importing `astro:env/server` in a `.astro` component's frontmatter that is also evaluated for client rendering; a barrel file (`index.ts`) re-exporting both client and server env modules.

Related errors


AI-assisted analysis of withastro/astro@d081033d5f (2026-08-12). Data as JSON: /api/errors/ad7ed1acf377ef5e. Report an issue: GitHub.