withastro/astro · error · AstroError

LocalImageUsedWrongly

LocalImageUsedWrongly

Error message

`Image`'s and `getImage`'s `src` parameter must be an imported image or a URL, it cannot be a string filepath. Received `${imageFilePath}`.

What it means

Thrown by verifyOptions() when src is a string that is not ESM-imported and looks like a local filesystem path. The check fires when the string starts with '/@fs/' or is not remote and does not start with '/'. Passing an absolute/relative filesystem path instead of an import or a URL is rejected.

Source

Thrown at packages/astro/src/assets/services/service.ts:164

	// `src` is missing or is `undefined`.
	if (!options.src || (!isRemoteImage(options.src) && !isESMImportedImage(options.src))) {
		throw new AstroError({
			...AstroErrorData.ExpectedImage,
			message: AstroErrorData.ExpectedImage.message(
				JSON.stringify(options.src),
				typeof options.src,
				JSON.stringify(options, (_, v) => (v === undefined ? null : v)),
			),
		});
	}

	if (!isESMImportedImage(options.src)) {
		// User passed an `/@fs/` path or a filesystem path instead of the full image.
		if (
			options.src.startsWith('/@fs/') ||
			(!isRemotePath(options.src) && !options.src.startsWith('/'))
		) {
			throw new AstroError({
				...AstroErrorData.LocalImageUsedWrongly,
				message: AstroErrorData.LocalImageUsedWrongly.message(options.src),
			});
		}

		// For remote images, width and height are explicitly required as we can't infer them from the file
		let missingDimension: 'width' | 'height' | 'both' | undefined;
		if (!options.width && !options.height) {
			missingDimension = 'both';
		} else if (!options.width && options.height) {
			missingDimension = 'width';
		} else if (options.width && !options.height) {
			missingDimension = 'height';
		}

		if (missingDimension) {
			throw new AstroError({
				...AstroErrorData.MissingImageDimension,

View on GitHub (pinned to d081033d5f)

Solutions

  1. Import local images as ESM assets: import img from './img.png'; then <Image src={img} />.
  2. Place public images under public/ and reference them by root-relative URL starting with '/' (e.g. '/img.png').
  3. For remote images use the full https:// URL.
  4. Never pass raw disk paths or '/@fs/' URLs to Image/getImage.

Example fix

// before
<Image src='/@fs/home/user/proj/img.png' />

// after
import img from '../assets/img.png';
<Image src={img} />
Defensive patterns

Strategy: validation

Validate before calling

function isLocalDiskPath(src: string): boolean {
  return src.startsWith('/@fs/') || (!/^https?:\/\//.test(src) && !src.startsWith('/'));
}

Prevention

When it happens

Trigger: Calling the service with options.src equal to '/@fs/abs/path/img.png', a relative path like 'assets/img.png', or 'C:/images/img.png' on Windows — i.e. a local path used where an import or a public-rooted/remote URL is required.

Common situations: User writes <Image src='/path/to/local.png' /> intending a local image instead of `import img from './img.png'`, or pastes an absolute disk path. Also a stray '/@fs/' URL leaking from dev tooling.

Related errors


AI-assisted analysis of withastro/astro@d081033d5f (2026-08-12). Data as JSON: /api/errors/8fd6d46e9be68dd6. Report an issue: GitHub.