From 5c485f0629dec92a524ba51aaf30e75e659fb5aa Mon Sep 17 00:00:00 2001 From: Leonardo Ortiz Date: Wed, 12 Aug 2026 13:08:32 -0300 Subject: [PATCH 1/2] fix(astro): set the image endpoint entrypoint so /_image works MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `image.endpoint` is an object (`{ route, entrypoint }`), not a string, so `config.image.endpoint ?? "astro/assets/endpoint/node"` never fired — the object is always truthy. The entrypoint was therefore never set, no /_image route was built, and every optimized image 404'd at runtime while the adapter advertised `sharpImageService: "stable"`. Sets `entrypoint` inside the object, keeping Astro's dev endpoint for `astro dev` and using the Node one for builds, and passes `route` through so a custom route still works. Mirrors @astrojs/node's own handling. Verified on App Hosting deployments of Astro 5, 6 and 7, checking the served image's dimensions, file type and quality. --- .../experimental/adapter-astro/src/index.ts | 8 ++++++-- .../adapter-astro/src/utils.spec.ts | 18 ++++++++++++++++++ .../experimental/adapter-astro/src/utils.ts | 15 +++++++++++++++ 3 files changed, 39 insertions(+), 2 deletions(-) diff --git a/packages/@apphosting/experimental/adapter-astro/src/index.ts b/packages/@apphosting/experimental/adapter-astro/src/index.ts index e51330720..568155fca 100644 --- a/packages/@apphosting/experimental/adapter-astro/src/index.ts +++ b/packages/@apphosting/experimental/adapter-astro/src/index.ts @@ -11,6 +11,7 @@ import type { Options, UserOptions } from "./types.js"; import { createConfigPlugin, getPackageVersion, + resolveImageEntrypoint, ASTRO_PACKAGE_NAME, SUPPORTED_ASTRO_FEATURES, usesVirtualConfig, @@ -59,10 +60,13 @@ export default function createIntegration(userOptions: UserOptions): AstroIntegr return { name: "@apphosting/astro-adapter", hooks: { - "astro:config:setup": ({ updateConfig, config }) => { + "astro:config:setup": ({ updateConfig, config, command }) => { updateConfig({ image: { - endpoint: config.image.endpoint ?? "astro/assets/endpoint/node", + endpoint: { + route: config.image.endpoint.route, + entrypoint: resolveImageEntrypoint(config.image.endpoint.entrypoint, command), + }, }, vite: { ssr: { diff --git a/packages/@apphosting/experimental/adapter-astro/src/utils.spec.ts b/packages/@apphosting/experimental/adapter-astro/src/utils.spec.ts index e16154115..1540651c4 100644 --- a/packages/@apphosting/experimental/adapter-astro/src/utils.spec.ts +++ b/packages/@apphosting/experimental/adapter-astro/src/utils.spec.ts @@ -4,6 +4,24 @@ import type { Options } from "./types.js"; const importUtils = import("@apphosting/astro-adapter/dist/utils.js"); +describe("resolveImageEntrypoint", () => { + it("serves builds from the Node endpoint", async () => { + const { resolveImageEntrypoint } = await importUtils; + assert.equal(resolveImageEntrypoint(undefined, "build"), "astro/assets/endpoint/node"); + }); + + it("serves `astro dev` from the dev endpoint", async () => { + const { resolveImageEntrypoint } = await importUtils; + assert.equal(resolveImageEntrypoint(undefined, "dev"), "astro/assets/endpoint/dev"); + }); + + it("keeps a user-configured entrypoint for every command", async () => { + const { resolveImageEntrypoint } = await importUtils; + assert.equal(resolveImageEntrypoint("my/custom/endpoint", "build"), "my/custom/endpoint"); + assert.equal(resolveImageEntrypoint("my/custom/endpoint", "dev"), "my/custom/endpoint"); + }); +}); + describe("createConfigPlugin", () => { const config = { mode: "standalone", host: true, port: 4321 } as Options; diff --git a/packages/@apphosting/experimental/adapter-astro/src/utils.ts b/packages/@apphosting/experimental/adapter-astro/src/utils.ts index 786daad37..098374369 100644 --- a/packages/@apphosting/experimental/adapter-astro/src/utils.ts +++ b/packages/@apphosting/experimental/adapter-astro/src/utils.ts @@ -22,6 +22,21 @@ export const SUPPORTED_ASTRO_FEATURES: AstroAdapterFeatureMap = { i18nDomains: "experimental", envGetSecret: "stable", } as const; +const IMAGE_ENDPOINT = { + dev: "astro/assets/endpoint/dev", + node: "astro/assets/endpoint/node", +} as const; + +/** + * Resolves which entrypoint serves the image endpoint. A user-configured entrypoint always wins; + * otherwise Astro's dev endpoint serves `astro dev` and its Node endpoint serves builds. + * @param entrypoint The entrypoint already set on the Astro config, if any. + * @param command The Astro command running this build. + * @return The entrypoint to serve the image endpoint from. + */ +export function resolveImageEntrypoint(entrypoint: string | undefined, command: string): string { + return entrypoint ?? (command === "dev" ? IMAGE_ENDPOINT.dev : IMAGE_ENDPOINT.node); +} /** * Reads the version of a package as installed in the user's project. From 5ae2444682b9329998f68b818b4abe676be6ec52 Mon Sep 17 00:00:00 2001 From: Yuangwang Date: Wed, 12 Aug 2026 12:19:55 -0400 Subject: [PATCH 2/2] Apply suggestions from code review Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> --- packages/@apphosting/experimental/adapter-astro/src/index.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/@apphosting/experimental/adapter-astro/src/index.ts b/packages/@apphosting/experimental/adapter-astro/src/index.ts index 568155fca..413d1c5ca 100644 --- a/packages/@apphosting/experimental/adapter-astro/src/index.ts +++ b/packages/@apphosting/experimental/adapter-astro/src/index.ts @@ -64,8 +64,8 @@ export default function createIntegration(userOptions: UserOptions): AstroIntegr updateConfig({ image: { endpoint: { - route: config.image.endpoint.route, - entrypoint: resolveImageEntrypoint(config.image.endpoint.entrypoint, command), + route: config.image.endpoint?.route, + entrypoint: resolveImageEntrypoint(config.image.endpoint?.entrypoint, command), }, }, vite: {