{"record":{"id":"cf8df9c57707f344","repo":"withastro/astro","slug":"configured-image-service-is-not-a-local-service-cf8df9","errorCode":null,"errorMessage":"Configured image service is not a local service","messagePattern":"Configured image service is not a local service","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/astro/src/assets/endpoint/shared.ts","lineNumber":48,"sourceCode":"\t\treturn Buffer.from(await res.arrayBuffer());\n\t} catch {\n\t\treturn undefined;\n\t}\n}\n\nexport const handleImageRequest = async ({\n\trequest,\n\tloadLocalImage,\n\tlogger,\n}: {\n\trequest: Request;\n\tloadLocalImage: (src: string, baseUrl: URL) => Promise<Buffer | undefined>;\n\tlogger: AstroRuntimeLogger;\n}) => {\n\tconst imageService = await getConfiguredImageService();\n\n\tif (!('transform' in imageService)) {\n\t\tthrow new Error('Configured image service is not a local service');\n\t}\n\n\tconst url = new URL(request.url);\n\tconst transform = await imageService.parseURL(url, imageConfig, logger);\n\n\tif (!transform?.src) {\n\t\treturn new Response('Invalid request', { status: 400 });\n\t}\n\n\t// Reject requests that attempt to convert a non-SVG source to SVG output.\n\t// This mirrors the same guard in verifyOptions() that protects the <Image> component path.\n\tif (transform.format === 'svg') {\n\t\tconst sourceFormat = inferSourceFormat(transform.src);\n\t\tif (sourceFormat !== 'svg') {\n\t\t\treturn new Response('Cannot convert non-SVG source to SVG format', { status: 403 });\n\t\t}\n\t}\n","sourceCodeStart":30,"sourceCodeEnd":66,"githubUrl":"https://github.com/withastro/astro/blob/e294953aa8aadd98d5be92e60a03037b05dbdfd4/packages/astro/src/assets/endpoint/shared.ts#L30-L66","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Configure a local service with `transform` (e.g. `astro/assets/services/sharp`) in `astro.config.mjs` for the environment where SSR/preview runs.","Verify no adapter/integration overrides `image.service` after your config; log or inspect the resolved config with `astro sync`/debug output.","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)."],"exampleFix":"// astro.config.mjs — before\nexport default defineConfig({ image: { service: { entrypoint: 'astro/assets/services/passthrough' } } });\n\n// astro.config.mjs — after\nexport default defineConfig({ image: { service: { entrypoint: 'astro/assets/services/sharp' } } });","handlingStrategy":"validation","validationCode":"// preflight before astro preview / SSR deploy: ensure a transform-capable service\nimport fs from 'node:fs';\nexport function assertLocalImageService(configFile = './astro.config.mjs') {\n  const cfg = fs.readFileSync(configFile, 'utf8');\n  if (/passthrough/.test(cfg)) {\n    throw new Error('handleImageRequest requires a local image service; passthrough is configured');\n  }\n}","typeGuard":"function isLocalService(service: unknown): service is { transform: (input: Uint8Array, t: unknown, cfg: unknown) => Promise<{ data: Uint8Array; format: string }> } {\n  return typeof service === 'object' && service !== null && typeof (service as any).transform === 'function';\n}","tryCatchPattern":null,"preventionTips":["Pin image.service.entrypoint explicitly in config so adapters cannot surprise you.","Run a post-build SSR smoke request against one optimized image URL before shipping.","Wrap adapter image handlers in an error-reporting middleware since this throw is unhandled."],"tags":["images","image-service","ssr","adapter","configuration"],"backgroundTag":"image-service-misconfigured","analyzedSha":"e294953aa8aadd98d5be92e60a03037b05dbdfd4","analyzedAt":"2026-08-18T18:48:03.901Z","contentChangedAt":"2026-08-18T18:48:03.901Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}