Visitar URL original
Compile images by default with Cloudflare adapter by adamchal · Pull Request #17842 · withastro/astro · GitHub
Skip to content

Compile images by default with Cloudflare adapter - #17842

Merged
matthewp merged 2 commits into
withastro:mainfrom
adamchal:cloudflare-image-service-default
Sep 10, 2026
Merged

matthewp merged 2 commits into
withastro:mainfrom
adamchal:cloudflare-image-service-default

Conversation

@adamchal

@adamchal adamchal commented Aug 27, 2026 •

Copy link
Copy Markdown
Member

Changes

  • Prerendered images are now optimized at build time by default using the workerd Images binding.

Testing

  • Config: Omitted imageService (default) and explicit 'cloudflare-binding' (shorthand) enable build-time transforms.
  • Binding execution: Both forms generate real WebP assets without falling back to Sharp.
  • Static output: Explicit and inferred static builds emit hashed assets for Image, Picture, and getImage, with no /_image URLs or Worker.
  • Server output: Explicit and inferred server builds generate assets for prerendered routes while serving on-demand images through /_image.
    • Confirms the Images binding is configured and Sharp stays out of the Worker.

Docs

N/a. Fixes expected behavior.

Closes #16931

@changeset-bot

changeset-bot Bot commented Aug 27, 2026 •

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 2e74736

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

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 Aug 27, 2026

@matthewp matthewp 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.

Blocking a merge since this is a major. The Cloudflare vite plugin is going to be released soon and that would likely need a major so want to coordinate that and this being released as the same time.

@adamchal
adamchal force-pushed the cloudflare-image-service-default branch from 59b5e92 to 1ac1843 Compare August 31, 2026 14:08
@adamchal

Copy link
Copy Markdown
Member Author

@matthewp I’m wrestling with two things annoying items:

  1. I think my changeset is clear. But, if a user really tries to understand the imageService docs options, most will give up because it is just too difficult to understand the nuance—I still get myself confused.
  2. Sharp is an optional dependency and should stay that way, but after this PR, if you do not have Sharp installed, your build will fail with the default and not give the user any real guidance as to why. I wanted to at least hint to the user “hey you need to install Sharp or set imageService: 'cloudflare-binding',” but this is not really possible without creating more swirl.

Otherwise, I think the PR is ready for review and I can open a docs PR to complement this change. I just don’t want to waste a major on creating more confusion, but I’m also afraid that I don’t have a cleaner fix for these nags that would blow up this PR into something too big. I keep coming back to the premise that this will close #16931. And just crossing my fingers that it won’t generate consternation for folks finding out that their builds are breaking.

@adamchal
adamchal marked this pull request as ready for review August 31, 2026 20:42
@matthewp

matthewp commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

@adamchal better to double-guess these things than put out a release that you have doubts on. Let me think about these problems some more and get back with you.

@matthewp

matthewp commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

Yeah I don't like this either. I think we need to figure out some other solution here. It seems to me like it's clear in 2/3 output values:

  • static - clearly you should have sharp installed.
  • server - you shouldn't have to have sharp installed.
  • undefined - Well, we don't know until we build.

If only it were possible to set the image service before generation we could set it to compile at that point, knowing that there are images to possibly build.

What's currently happening? If you have a purely static site with images those images just don't get built?

@matthewp

matthewp commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

going to ping @Princesseuh for input on this as well.

@adamchal

adamchal commented Sep 2, 2026

Copy link
Copy Markdown
Member Author

The current issue is that all images are emitted as <img src="/_image…"/> which is an issue if you don’t get a Worker to handle /_image requests.

In order to get a worker, you either need:

  1. to explicitly set output: "server" in your Astro config; or
  2. use something that triggers the core auto-flip to server (ex. you have a prerender = false route, or enable Actions, etc.).

But, if you don’t, then you’re going to have broken 404 images.

Thinking through this, one option might be to resolve the imageService default from buildOutput since it is settled by the time astro:config:done runs in a build.

This allows server sites (explicitly or through the auto-flip) to have the same imageService default and unchanged behavior today. And for those static sites that cannot resolve Sharp, it will fail at build time rather than silently producing broken images at runtime.

Two “dynamic default” downsides:

  1. server sites could truly benefit by having their pre-rendered images handled at build time rather than relying on the Worker at runtime, but will not with this dynamic default approach. Not the end of the world—if they care, they can set { build: 'compile', runtime: 'cloudflare-binding' } explicitly.
  2. It’s hard to write documentation for this dynamic default behavior, but maybe that’s just a “hard for me” thing.

Also, I feel like this approach could land as a patch since it only changes the behavior of already broken static sites: it fixes the ones that can resolve Sharp, and fails the build for the ones that cannot instead of silently generating broken images.

And for the majority of static site build environments, they will likely have Sharp available. It’s only the environments that --omit=optional, or don’t have a prebuilt binary for the platform, that will encounter build-time failures with this approach.

@matthewp

matthewp commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

Good point, dynamic defaults are confusing and you almost always regret them when you do that. Going to talk with some people on the team to get their opinion and will get back with you. Appreciate all of the effort this has taken.

@adamchal

adamchal commented Sep 3, 2026

Copy link
Copy Markdown
Member Author

It's also possible I'm just in too deep and this is an acceptable default as-is PR based on a major bump.

Lmk what they think. I do think the default in this PR is better than what exists today: failing a build because my CI avoids optional deps is better than deploying with broken images.

@matthewp

matthewp commented Sep 4, 2026

Copy link
Copy Markdown
Contributor

@adamchal after talking about it with @Princesseuh I think I'm even more confused than I was before 😅. I'm now not understanding why cloudflare-binding doesn't work locally. Let's leave prerenderEnvironment: 'node' aside for a second.

This is the part that confuses me:

The current issue is that all images are emitted as which is an issue if you don’t get a Worker to handle /_image requests.

In order to get a worker, you either need:

to explicitly set output: "server" in your Astro config; or
use something that triggers the core auto-flip to server (ex. you have a prerender = false route, or enable Actions, etc.).

But, if you don’t, then you’re going to have broken 404 images.

If you are not using output: 'server' then prererendering doesn't run inside workerd? Is this intentional or a bug (either could be my fault here).

@adamchal

adamchal commented Sep 4, 2026

Copy link
Copy Markdown
Member Author

@matthewp Yes, prerendering still runs inside workerd when the resolved build output is static, with the IMAGES binding available. The confusing part is that the current imageService: 'cloudflare-binding' shorthand does not actually invoke that binding during the prerender run.

The shorthand resolves to buildService: 'cloudflare-binding' and runtimeService: 'cloudflare-binding', but transformAtBuild is false. That means workerd gets the image-service stub, whose transform is effectively a passthrough, for URL generation rather than the build-time static-image collection path.

This hidden complexity exists because I flagged in this Discord thread during #16194 that:

Adam Chalemian — 7/26/26, 4:04 PM (New York)
One thing that should be called out about using the cloudflare-binding during build is that it does not use Astro’s asset caching. For non-trivial projects, this can have significant CI/CD build time implications.

As a result, the way to avoid CI from compiling the same images on every build was to introduce that hidden flag on the default config.

That was good, but we didn’t realized at the time that it would create broken images on purely static builds. So, this PR is just trying to find a better default.

@matthewp

matthewp commented Sep 9, 2026

Copy link
Copy Markdown
Contributor

@adamchal I've been thinking more about this and I think cloudflare-binding is the correct default. Changing the default is trading away correctness for speed. We should be able to fix the problem of it not using the cache another way.

So I think the thing to fix here is that transformAtBuild is false by default. Can we just flip this to true? Maybe it should be false for the ssr environment and false for prerender?

@adamchal

adamchal commented Sep 9, 2026

Copy link
Copy Markdown
Member Author

I don’t love making the default a known CI churner—BUT, I do agree with you: cloudflare-binding should be the default and it should transformAtBuild. I will refocus this PR on that. The changeset could become a patch.

I’m also going to take a look at what would be involved in extending the asset caching and allowing participation from an adapter.

@matthewp

matthewp commented Sep 9, 2026

Copy link
Copy Markdown
Contributor

@adamchal Happy to have you look at asset caching, I looked a bit into it and it seems doable but big.

It would be nice to keep this one focused on fixing the bug and then do asset caching as a separate PR.

@adamchal
adamchal force-pushed the cloudflare-image-service-default branch from 1ac1843 to 2e74736 Compare September 10, 2026 14:13
@adamchal

Copy link
Copy Markdown
Member Author

@matthewp this PR is now simply one functional code change:

- transformAtBuild: mode === 'compile',
+ transformAtBuild: mode === 'compile' || mode === 'cloudflare-binding',
  • 🟢 The .changeset became a patch.
  • 🟢 No doc updates required.
    • Restored existing behavior where the adapter replaces a user-configured image.service with its stub (cloudflare-binding). Users continue to use build: 'compile' for that.
    • That was the other change for doc updates and a major.
  • 🟢 Tests expanded just to cover the default, shorthand, and compound imageService configuration options.
  • 🟢 The defaults no longer have the issue of Sharp being an optional dependency.
  • ⚠️ prerenderEnvironment: 'node' + this default will continue to be broken: fixing this case brings us back to Sharp being an optional dependency.
    • I think this is fine to leave alone since this is addressed in the docs and is solved by explicitly defining your imageService.
    • ℹ️ Tests also do not cover this use-case.
  • ✍️ I will give the “adapter asset caching hook” a draft PR in a few days.

@matthewp

Copy link
Copy Markdown
Contributor

@adamchal Thanks so much for sticking with this!

@matthewp
matthewp merged commit d68db73 into withastro:main Sep 10, 2026
25 checks passed
@astrobot-houston astrobot-houston mentioned this pull request Sep 10, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

pkg: integration Related to any renderer integration (scope)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

@astrojs/cloudflare: default imageService generates 404s with static output

2 participants