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 itView on GitHub (pinned to d081033d5f)
Solutions
- 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).
- Split shared utilities so server-only imports live in a separate file the browser never imports.
- If the value is needed client-side, declare it in `env.schema` with `access: 'public'` and import it from `astro:env/client` instead.
- 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
- Keep server-env imports in files only endpoints/middleware import.
- Avoid barrel files that re-export astro:env/server.
- Run `astro check` and review the client bundle for server module leakage.
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
- EnvPrefixConflictsWithSecret
- ServerOnlyModule
- RemoteImageNotAllowed
- UnsupportedImageFormat
- RemoteImageNotAllowed
AI-assisted analysis of withastro/astro@d081033d5f (2026-08-12).
Data as JSON: /api/errors/ad7ed1acf377ef5e.
Report an issue: GitHub.