withastro/astro · error · AstroError
ServerOnlyModule
ServerOnlyModule
Error message
The "astro:content" module is only available server-side.
What it means
The astro:content virtual module — getCollection, getEntry, render, and config types — is generated for server bundles only. When the module is requested from a client environment build, such as a hydrated island importing it, the plugin throws ServerOnlyModule to stop the dependency on server-only infrastructure from reaching the browser bundle.
Solutions
- Fetch entries in an .astro page or component and pass plain serializable data as props into the island.
- Remove astro:content imports from any file reachable from a client: directive or an inline <script>.
- For type-only usage, use import type so the import is erased at compile time.
- Split shared modules into server-only and client-safe variants so data access stays on the server.
Example fix
// before: Island.jsx hydrated with client:load
import { getEntry } from 'astro:content';
export default function Island({ slug }) { /* uses getEntry */ }
// after: page.astro fetches server-side and passes data
---
import { getEntry } from 'astro:content';
import Island from '../components/Island.jsx';
const post = await getEntry('blog', Astro.params.slug);
---
<Island client:load post={post.data} /> Defensive patterns
Strategy: validation
Validate before calling
// Static check: no file under a client-hydrated component dir may import astro:content
import { readdir, readFile } from 'node:fs/promises';
for (const file of await readdir('src/components/islands', { recursive: true })) {
if (!/\.(jsx|tsx|svelte|vue)$/.test(String(file))) continue;
const src = await readFile(`src/components/islands/${file}`, 'utf8');
if (src.includes('astro:content')) console.error(`astro:content imported in island ${file}`);
} Prevention
- Fetch content server-side in .astro files and pass data as props to islands.
- Use import type { CollectionEntry } for types — type-only imports are erased.
- Keep shared client/server modules split so server-only data access cannot leak into the client graph.
When it happens
Trigger: import { getEntry } from 'astro:content' inside a component hydrated with client:load/client:visible (React, Svelte, Vue, Solid); a <script> processed for the browser importing a module that imports astro:content; framework client entrypoints re-exporting a file that pulls it in.
Common situations: Moving data fetching into a shared UI component that later gets hydrated; shared constants files re-exporting from astro:content; refactoring islands so previously server-only code lands in the client graph.
Related errors
- [astro:actions] `defineAction()` unexpectedly used on the…
- [astro:actions] `getActionContext()` unexpectedly used on…
- Astro components cannot be used in the browser. Tried to…
- A collision will result in a hard error in following…
- A content collection is defined with legacy features (e.g…
AI-assisted analysis of withastro/astro@157c500c38 (2026-08-18).
Data as JSON: /api/errors/3ee4412df2fd358e.
Report an issue: GitHub.
Appendix: source
Thrown at packages/astro/src/content/vite-plugin-content-virtual-mod.ts:431
async function generateContentEntryFile({
settings,
isClient,
}: {
settings: AstroSettings;
fs: typeof nodeFs;
isClient: boolean;
}) {
const contentPaths = getContentPaths(
settings.config,
undefined,
settings.config.legacy?.collectionsBackwardsCompat,
);
const relContentDir = rootRelativePath(settings.config.root, contentPaths.contentDir);
let virtualModContents: string;
if (isClient) {
throw new AstroError({
...AstroErrorData.ServerOnlyModule,
message: AstroErrorData.ServerOnlyModule.message('astro:content'),
});
} else {
virtualModContents = nodeFs
.readFileSync(contentPaths.virtualModTemplate, 'utf-8')
.replace('@@CONTENT_DIR@@', relContentDir)
.replace(
'/* @@LIVE_CONTENT_CONFIG@@ */',
contentPaths.liveConfig.exists
? // Dynamic import so it extracts the chunk and avoids a circular import
`const liveCollections = (await import(${JSON.stringify(fileURLToPath(contentPaths.liveConfig.url))})).collections;`
: 'const liveCollections = {};',
);
}
return virtualModContents;
}View on GitHub (pinned to 157c500c38)