withastro/astro · error · AstroError

ServerOnlyModule

ServerOnlyModule

Error message

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

What it means

The virtual module `astro:config/server` exposes the Astro config to server-side code (it reads the serialized manifest). It only exists in server environments; when it is resolved from the client environment the manifest Vite plugin throws ServerOnlyModule, meaning you imported `astro:config/server` (directly or transitively) into browser-bound code. Use `astro:config/client` there - it inlines only the public subset of config values.

Solutions

  1. Switch the import to `astro:config/client` in browser-reachable code.
  2. Split shared config helpers into server and client variants so server config never enters the client graph.
  3. Pass needed config values from server components as props instead of importing the server module in client code.

Example fix

// before - client-bundled module
import { config } from 'astro:config/server';

// after
import { config } from 'astro:config/client';
Defensive patterns

Strategy: type-guard

Type guard

// in shared code: pick the right virtual module for the environment
const getConfig = import.meta.env.SSR
  ? () => import('astro:config/server')
  : () => import('astro:config/client');

Prevention

When it happens

Trigger: Importing astro:config/server inside an inline `<script>` tag or a `client:*` component; a shared utility module that reads config and is imported by both server endpoints and client scripts; importing it in a static page's client bundle.

Common situations: Wanting base/trailingSlash config in client-side navigation helpers and grabbing the wrong module; refactors that move a config-reading helper into a shared file; SSR-to-static migrations pulling formerly server-only imports into the client graph.

Related errors


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

Appendix: source

Thrown at packages/astro/src/manifest/virtual-module.ts:88

					return RESOLVED_VIRTUAL_CLIENT_ID;
				}
			},
		},
		load: {
			filter: {
				id: new RegExp(`^(${RESOLVED_VIRTUAL_SERVER_ID}|${RESOLVED_VIRTUAL_CLIENT_ID})$`),
			},
			handler(id) {
				if (id === RESOLVED_VIRTUAL_CLIENT_ID) {
					// astro:config/client inlines values directly from settings instead of
					// importing from virtual:astro:manifest to avoid pulling server-only
					// virtual modules (virtual:astro:routes, virtual:astro:pages) into the
					// client environment where they are not available.
					return { code: clientConfigCode };
				}
				if (id === RESOLVED_VIRTUAL_SERVER_ID) {
					if (this.environment.name === ASTRO_VITE_ENVIRONMENT_NAMES.client) {
						throw new AstroError({
							...AstroErrorData.ServerOnlyModule,
							message: AstroErrorData.ServerOnlyModule.message(VIRTUAL_SERVER_ID),
						});
					}
					const code = `
import { manifest } from '${SERIALIZED_MANIFEST_ID}'
import { fromRoutingStrategy } from "astro/app";

let i18n = undefined;
if (manifest.i18n) {
 i18n = {
   defaultLocale: manifest.i18n.defaultLocale,
   locales: manifest.i18n.locales,
   routing: fromRoutingStrategy(manifest.i18n.strategy, manifest.i18n.fallbackType),
   fallback: manifest.i18n.fallback,
   domains: manifest.i18n.domains,
 };
}

View on GitHub (pinned to 52e6c34790)