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` provides server-only (secret) environment variables and exists only in the server environment. Astro's env Vite plugin throws ServerOnlyModule when that module is resolved from a client environment - meaning you imported it, directly or transitively, into code that ships to the browser.

Solutions

  1. In client-reachable code, import from `astro:env/client` instead - secret values can never be exposed to the browser by design.
  2. Split shared modules: keep secret reads in files imported only by server code (endpoints, middleware, server components).
  3. If a `client:*` component needs configuration, pass it as props from the server rather than importing astro:env/server inside the component.

Example fix

// before - module bundled for the client
import { SECRET_API_KEY } from 'astro:env/server';

// after - public value via the client module, secrets passed as props
import { PUBLIC_API_URL } from 'astro:env/client';
Defensive patterns

Strategy: type-guard

Type guard

// in shared code that must never pull server env into the client graph
function assertServerEnv(): void {
  if (!import.meta.env.SSR) {
    throw new Error('this module reads astro:env/server and must not be bundled for the client');
  }
}

Prevention

When it happens

Trigger: importing `astro:env/server` in a file bundled for the client: an inline `<script>` tag, a framework component with a `client:*` directive, or a shared utility imported by both server endpoints and client code.

Common situations: Centralizing env access in a shared lib/env.ts imported everywhere; refactoring a helper that reads a secret into a file also reachable from the browser; converting an SSR page to static and having its formerly server-only imports become part of the client graph.

Related errors


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

Appendix: 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 52e6c34790)