withastro/astro · error · AstroError

GetImageNotUsedOnServer

GetImageNotUsedOnServer

Error message

`getImage()` should only be used on the server. To use images on the client, render the `src` from `getImage()` during the server render, then pass it to the client for usage.

What it means

Astro exposes `getImage()` through a virtual module that is environment-aware. When that module is bundled for the client, `getImage` is replaced by a stub that throws GetImageNotUsedOnServer at call time (vite-plugin-assets.ts:189). Image optimization is a server-side operation; calling it in browser-executed code is unsupported.

Solutions

  1. Move the `getImage()` call into server-executed code (.astro frontmatter, endpoints, SSR components) and pass the resulting `src`/attributes down as props
  2. If a shared helper must exist, branch on `import.meta.env.SSR` and only call getImage on the server branch
  3. For client-driven images, use plain <img> with a URL the server already prepared

Example fix

// before (client island)
import { getImage } from 'astro/assets';
const img = await getImage({ src: hero }); // throws in browser

// after (server component passes result down)
// Server.astro: const img = await getImage({ src: hero });
// <Island imgSrc={img.src} imgAttrs={img.attributes} />
Defensive patterns

Strategy: validation

Validate before calling

// keep getImage out of client bundles by branching on the environment
export async function safeGetImage(options) {
  if (!import.meta.env.SSR) {
    throw new Error('safeGetImage must only be called during server rendering');
  }
  const { getImage } = await import('astro/assets');
  return getImage(options);
}

Type guard

function isServerRender(): boolean {
  return import.meta.env.SSR === true;
}

Try / catch

try {
  const img = await getImage(options);
} catch (err) {
  if (err instanceof AstroError && err.code === 'GetImageNotUsedOnServer') {
    throw new Error('Move this getImage() call to .astro frontmatter or an API route');
  }
  throw err;
}

Prevention

When it happens

Trigger: Importing `getImage` (virtual:get-image / astro:assets re-export) inside a hydrated component, client script, or framework component that ships to the browser, then calling it there; a shared utility used by both server pages and client islands that calls getImage at module scope or in an event handler.

Common situations: Refactoring a .astro page into a React/Preact/Svelte island and moving the getImage call along with it; calling getImage inside onClick/useEffect; library code that assumed Node always executes it.

Related errors


AI-assisted analysis of withastro/astro@e294953aa8 (2026-09-09). Data as JSON: /api/errors/54c2792129d833be. Report an issue: GitHub.

Appendix: source

Thrown at packages/astro/src/assets/vite-plugin-assets.ts:224

			},
			load: {
				filter: {
					id: new RegExp(`^(${RESOLVED_VIRTUAL_MODULE_ID}|${RESOLVED_VIRTUAL_GET_IMAGE_ID})$`),
				},
				handler(id) {
					if (id === RESOLVED_VIRTUAL_GET_IMAGE_ID) {
						// Lightweight module exporting only getImage + imageConfig.
						// No component references (Image, Picture, Font) to avoid TDZ
						// errors when the content runtime and component pages are
						// bundled into the same prerender chunk (see #16036).
						const isServerEnvironment = isAstroServerEnvironment(this.environment);
						const getImageExport = isServerEnvironment
							? `${RUNTIME_LOGGER_SETUP}
								import { getImage as getImageInternal } from "astro/assets";
								export const getImage = async (options) => await getImageInternal(options, imageConfig, _runtimeLogger);`
							: `import { AstroError, AstroErrorData } from "astro/errors";
								export const getImage = async () => {
									throw new AstroError(
										AstroErrorData.GetImageNotUsedOnServer.message,
										AstroErrorData.GetImageNotUsedOnServer.hint,
									);
								};`;

						const assetQueryParams = settings.adapter?.client?.assetQueryParams
							? `new URLSearchParams(${JSON.stringify(
									Array.from(settings.adapter.client.assetQueryParams.entries()),
								)})`
							: 'undefined';

						return {
							code: `
								export const imageConfig = ${JSON.stringify(settings.config.image)};
								Object.defineProperty(imageConfig, 'assetQueryParams', {
									value: ${assetQueryParams},
									enumerable: false,
									configurable: true,

View on GitHub (pinned to e294953aa8)