withastro/astro · error · Error

Configured image service is not a local service

Error message

Configured image service is not a local service

What it means

Same guard as the dev endpoint, but in `handleImageRequest` (packages/astro/src/assets/endpoint/shared.ts:45), the shared handler adapters and preview servers use to serve optimized images. Before parsing the request it asserts the configured image service has a `transform` method; a non-local service (passthrough, URL-rewriting CDN service, adapter-injected service) cannot process requests here. Unlike generic.ts, this throw is not wrapped in try/catch, so it propagates as an unhandled server error.

Solutions

  1. Configure a local service with `transform` (e.g. `astro/assets/services/sharp`) in `astro.config.mjs` for the environment where SSR/preview runs.
  2. Verify no adapter/integration overrides `image.service` after your config; log or inspect the resolved config with `astro sync`/debug output.
  3. If you must keep a non-local service, stop routing optimized image requests to the app (use the CDN URLs the service generates, or plain `<img>` tags).

Example fix

// astro.config.mjs — before
export default defineConfig({ image: { service: { entrypoint: 'astro/assets/services/passthrough' } } });

// astro.config.mjs — after
export default defineConfig({ image: { service: { entrypoint: 'astro/assets/services/sharp' } } });
Defensive patterns

Strategy: validation

Validate before calling

// preflight before astro preview / SSR deploy: ensure a transform-capable service
import fs from 'node:fs';
export function assertLocalImageService(configFile = './astro.config.mjs') {
  const cfg = fs.readFileSync(configFile, 'utf8');
  if (/passthrough/.test(cfg)) {
    throw new Error('handleImageRequest requires a local image service; passthrough is configured');
  }
}

Type guard

function isLocalService(service: unknown): service is { transform: (input: Uint8Array, t: unknown, cfg: unknown) => Promise<{ data: Uint8Array; format: string }> } {
  return typeof service === 'object' && service !== null && typeof (service as any).transform === 'function';
}

Prevention

When it happens

Trigger: Running SSR/preview behind an adapter that uses `handleImageRequest` while `image.service` resolves to a service lacking `transform` — e.g. `astro/assets/services/passthrough` or a custom service implementing only `getUrl` — and a page requests an optimized image URL.

Common situations: Adapters (node, edge, etc.) or integrations overriding `image.service` with a passthrough service; projects that disabled sharp in CI and then ran `astro preview` or SSR with `<Image>` components.

Related errors


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

Appendix: source

Thrown at packages/astro/src/assets/endpoint/shared.ts:48

		return Buffer.from(await res.arrayBuffer());
	} catch {
		return undefined;
	}
}

export const handleImageRequest = async ({
	request,
	loadLocalImage,
	logger,
}: {
	request: Request;
	loadLocalImage: (src: string, baseUrl: URL) => Promise<Buffer | undefined>;
	logger: AstroRuntimeLogger;
}) => {
	const imageService = await getConfiguredImageService();

	if (!('transform' in imageService)) {
		throw new Error('Configured image service is not a local service');
	}

	const url = new URL(request.url);
	const transform = await imageService.parseURL(url, imageConfig, logger);

	if (!transform?.src) {
		return new Response('Invalid request', { status: 400 });
	}

	// Reject requests that attempt to convert a non-SVG source to SVG output.
	// This mirrors the same guard in verifyOptions() that protects the <Image> component path.
	if (transform.format === 'svg') {
		const sourceFormat = inferSourceFormat(transform.src);
		if (sourceFormat !== 'svg') {
			return new Response('Cannot convert non-SVG source to SVG format', { status: 403 });
		}
	}

View on GitHub (pinned to e294953aa8)