Skip to content

fix(@astrojs/cloudflare): fix imageService: 'compile' silent noop with prerenderEnvironment: 'node' - #17347

Merged
alexanderniebuhr merged 9 commits into
mainfrom
triagebot/fix-17346
Aug 2, 2026
Merged

fix(@astrojs/cloudflare): fix imageService: 'compile' silent noop with prerenderEnvironment: 'node'#17347
alexanderniebuhr merged 9 commits into
mainfrom
triagebot/fix-17346

Conversation

@astrobot-houston

Copy link
Copy Markdown
Contributor

Summary

Fixes a bug where imageService: 'compile' produced unoptimized images (byte-for-byte source copies with incorrect extensions, e.g. PNG bytes in a .webp file) when prerenderEnvironment was set to 'node'. No warning or error was emitted — the build completed silently.

Closes #17346

Root Cause

Two interacting issues:

  1. collectStaticImages only existed on the workerd prerenderer. This method installs sharp (or a user-configured image service) into globalThis.astroAsset.imageService before the image generation pipeline runs. When prerenderEnvironment: 'node', the default Node prerenderer was used instead — which had no collectStaticImages, so image transforms fell back to the workerd passthrough stub, returning input buffers unchanged.

  2. The default prerender entrypoint was skipped when setPrerenderer was called. Astro skips setting the prerender entrypoint when settings.prerenderer is truthy, so restoring it manually was also required.

Fix

In packages/integrations/cloudflare/src/index.ts:

  • astro:build:start hook — Added an else if (hasBuildImageService) branch for prerenderEnvironment: 'node'. Wraps the default prerenderer with a collectStaticImages method that installs sharp (or the user's custom service) before image generation runs, mirroring the existing workerd prerenderer behavior.

  • astro:build:setup hook — Restores the default prerender entrypoint when prerenderEnvironment: 'node' and hasBuildImageService are both true, since it gets skipped when setPrerenderer is called.

Before: (before: 12kB, after: 12kB) — output .webp is actually PNG bytes
After: (before: 12kB, after: 0kB) — proper WebP output (~494 bytes)

Testing

Added packages/integrations/cloudflare/test/compile-image-service.test.ts with tests covering:

  • imageService: 'compile' with prerenderEnvironment: 'node' (the bug scenario)
  • User-configured custom image service with prerenderEnvironment: 'node'

All existing tests continue to pass.

… node (#17346)

When `imageService: 'compile'` was used with `prerenderEnvironment: 'node'`,
images were silently copied without optimization (PNG bytes in .webp files).

The root cause was that `collectStaticImages` — which installs sharp for
build-time transforms — only ran in the workerd prerenderer path. The node
prerender path used the workerd passthrough stub instead.

Fix: wrap the default prerenderer with a `collectStaticImages` method that
installs sharp (or the user's custom service) before image generation runs,
and restore the default prerender entrypoint that gets skipped when
`settings.prerenderer` is set.
@changeset-bot

changeset-bot Bot commented Jul 10, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 9d47fc6

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 40 packages
Name Type
@astrojs/cloudflare Patch
@test/astro-cloudflare-node-prerender-mdx Patch
@test/astro-cloudflare Patch
@test/astro-cloudflare-allowed-hosts Patch
@test/astro-cloudflare-astro-dev-platform Patch
@test/astro-cloudflare-astro-env Patch
@test/astro-cloudflare-binding-image-service Patch
@test/astro-cloudflare-cache-provider-wait-until Patch
@test/astro-cloudflare-cache-provider Patch
@test/astro-cloudflare-client-address Patch
@test/astro-cloudflare-compile-custom-image-service Patch
@test/astro-cloudflare-compile-image-service Patch
@test/astro-cloudflare-custom-entryfile Patch
@test/astro-cloudflare-dev-image-endpoint Patch
@test/astro-cloudflare-external-image-service Patch
@test/astro-cloudflare-external-redirects Patch
@test/astro-cloudflare-internal-redirects Patch
@test/astro-cloudflare-no-output Patch
@test/astro-cloudflare-prerender-node-env Patch
@test/astro-cloudflare-prerender-queue-consumers Patch
@test/astro-cloudflare-prerender-styles Patch
@test/astro-cloudflare-prerenderer-errors Patch
@test/astro-cloudflare-prerenderer-render-error Patch
@test/routing-priority-cloudflare Patch
@test/cf-server-entry Patch
@test/astro-cloudflare-server-island-prerender-framework Patch
@test/astro-cloudflare-sessions Patch
@test/astro-cloudflare-sql-import Patch
@test/cf-ssr-deps Patch
@test/astro-cloudflare-static Patch
@test/astro-cloudflare-svelte-rune-deps Patch
@test/astro-cloudflare-top-level-return Patch
@test/cf-user-optimize-deps Patch
@test/astro-cloudflare-vite-plugin Patch
@test/astro-cloudflare-with-base Patch
@test/astro-cloudflare-with-react Patch
@test/astro-cloudflare-with-solid-js Patch
@test/astro-cloudflare-with-svelte Patch
@test/astro-cloudflare-with-vue Patch
@test/astro-cloudflare-wrangler-preview-platform Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@github-actions github-actions Bot added the pkg: integration Related to any renderer integration (scope) label Jul 10, 2026
@ematipico ematipico self-assigned this Jul 10, 2026

@adamchal adamchal left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ematipico embarrassed I missed this one. Got a little fixated on the workerd prerenderer!

Read through the fix and it seems logical and the tests look good.

I also tested this fix locally to make sure that it does not regress anything in #17099 or #17285 on the workerd side.

@kitschpatrol

Copy link
Copy Markdown
Contributor

Hi, giving this a gentle bump in case someone has a minute to review! LGTM. 🙏

Comment thread packages/integrations/cloudflare/test/compile-image-service.test.ts
Comment thread packages/integrations/cloudflare/test/compile-image-service.test.ts
Comment thread packages/integrations/cloudflare/src/index.ts
Comment thread packages/integrations/cloudflare/src/index.ts
Comment thread packages/integrations/cloudflare/test/compile-image-service.test.ts Outdated

@alexanderniebuhr alexanderniebuhr left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ematipico I wanted to approve this, but saw you assigned yourself, so I just left some comments after my review. Would love if we can get this merged :)

Implementation is solid and works as expected, just the tests have some minor nits AFAIK

@ematipico ematipico removed their assignment Jul 25, 2026
@ematipico

Copy link
Copy Markdown
Member

@alexanderniebuhr it's all yours

@alexanderniebuhr

Copy link
Copy Markdown
Member

Thanks, I'll handle the changes and merge :)

@matthewp

matthewp commented Aug 2, 2026

Copy link
Copy Markdown
Contributor

@alexanderniebuhr do you still plan on resolving this one? Thanks!

Without cache isolation these tests pass on assets-cache hits from the
earlier workerd suites (which generate the identical transforms into the
shared node_modules/.astro cache), so they could not fail even with the
fix reverted.
…overage

With a user-configured image.service the Node prerender bundle already
loads the user service via virtual:image-service, so this scenario worked
before the fix — the test documents behavior rather than guarding the
#17346 regression.
…lSharp

The ERR_DLOPEN_FAILED rationale documented the skipRealSharp constant but
was left above the build-time generation suite when the constant moved to
the top of the file.
The restored entrypoint specifier and config shape duplicate the skip
condition in packages/astro/src/core/build/vite-build-config.ts; note
that they must stay in lockstep since drift silently reintroduces the
unoptimized-image failure mode this fix removes.
…s as defensive

With a user-configured image.service the prerender bundle already loads
the service via virtual:image-service; the re-import exists for symmetry
with the workerd prerenderer, not correctness.
@github-actions

github-actions Bot commented Aug 2, 2026

Copy link
Copy Markdown
Contributor

e18e dependency analysis

No dependency warnings found.

…taticImages

The raw entrypoint import can fail where the bundled service works (e.g.
TypeScript entrypoints on Node versions without type stripping), and any
registered static image implies the bundled service is already cached in
globalThis. Only import when the cache is empty — meaning no image was
rendered and the service goes unused — and never fail the build over it.
@alexanderniebuhr
alexanderniebuhr merged commit ce83c39 into main Aug 2, 2026
24 checks passed
@alexanderniebuhr
alexanderniebuhr deleted the triagebot/fix-17346 branch August 2, 2026 18:15
@astrobot-houston astrobot-houston mentioned this pull request Aug 2, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

fix verified Reporter confirmed the triage bot fix works pkg: integration Related to any renderer integration (scope)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

@astrojs/cloudflare: imageService: 'compile' is a silent noop with prerenderEnvironment: 'node'

6 participants