Skip to content

fix(media): load transformed images from storage, not via HTTP request#1549

Merged
ascorbic merged 2 commits into
mainfrom
feat/storage-backed-image-optimization
Jun 22, 2026
Merged

fix(media): load transformed images from storage, not via HTTP request#1549
ascorbic merged 2 commits into
mainfrom
feat/storage-backed-image-optimization

Conversation

@ascorbic

@ascorbic ascorbic commented Jun 19, 2026

Copy link
Copy Markdown
Collaborator

What does this PR do?

Fixes responsive image optimization images behind auth, proxy etc by loadig the source images form storage rather than via an HTTP request. The responsive-srcset feature (#1438) hands Astro's image service an absolute media URL (required — Astro's baseService.getURL only optimizes sources that pass isRemoteAllowed, and isRemoteAllowed rejects relative URLs outright). Astro's stock image endpoint then fetch()es that absolute URL to load the source bytes. On Cloudflare that's a self-subrequest to the Worker's own origin, which fails behind Cloudflare Access — surfacing as a 404 from /_image.

This wraps Astro's image endpoint with one that, for an EmDash media URL, reads the source bytes straight from the storage adapter and transforms them in place — the absolute href is never fetched, so there's no loopback and Access is never in the way. Every other image (bundled assets, allowed-remote, publicUrl CDN media) is delegated to the platform's stock endpoint unchanged.

  • The endpoint is platform-specific: Node uses astro:assets (sharp) and delegates to astro/assets/endpoint/generic; Cloudflare uses the IMAGES binding and delegates to @astrojs/cloudflare/image-transform-endpoint.
  • The components, image.remotePatterns, and astro:assets flow are unchanged from feat: responsive srcset for media via astro:assets #1438 — the fix is entirely in the endpoint. Image/getImage drive everything; there's no parallel route and no per-image component branching.
  • On by default wherever the image service is local (sharp, Cloudflare cloudflare-binding). Opt out with images: false. Raw-bytes fallbacks carry the same sandbox-CSP / Content-Disposition protections as the media file route.

Supersedes #1494 (keeps its IMAGES-binding transform, drops its parallel route + component branching). Design discussion: #1547.

Primarily a bug fix to #1438's already-intended behavior; it also adds an images opt-out and new package exports.

Type of change

  • Bug fix
  • Feature (requires maintainer-approved Discussion)
  • Refactor (no behavior change)
  • Translation
  • Documentation
  • Performance improvement
  • Tests
  • Chore (dependencies, CI, tooling)

Checklist

  • I have read CONTRIBUTING.md
  • pnpm typecheck passes
  • pnpm lint passes
  • pnpm test passes (or targeted tests for my change)
  • pnpm format has been run
  • I have added/updated tests for my changes (if applicable)
  • User-visible strings in the admin UI are wrapped for translation — n/a, no admin UI strings
  • I have added a changeset
  • New features link to an approved Discussion — n/a (bug fix); related design in RFC: Storage-backed image optimization #1547

AI-generated code disclosure

  • This PR includes AI-generated code — model/tool: Claude Opus 4.8 (Claude Code)

Screenshots / test output

# unit
Test Files  9 passed (9)
     Tests  131 passed (131)

# e2e (new image-optimization.spec.ts)
EMDASH_E2E_TARGET=node        → 1 passed (18.0s)
EMDASH_E2E_TARGET=cloudflare  → 1 passed (24.7s)

The e2e loads the seeded /posts/post-with-image, asserts the rendered <img> src goes through /_image, and fetches that URL to confirm the wrapped endpoint returns a real image (200, image/*) — on both sharp (Node) and the IMAGES binding (workerd).

Wrap Astro's image endpoint so EmDash media bytes are read straight from
the storage adapter instead of being fetched over HTTP. The stock endpoint
fetches the (absolute) media URL to load the source; on Cloudflare that is a
self-subrequest that fails behind Access / global_fetch_strictly_public,
404ing from /_image. The wrapped endpoint matches the internal media route by
pathname, reads bytes from storage (no fetch), and transforms them with sharp
(Node) or the IMAGES binding (Cloudflare); everything else delegates to the
stock endpoint unchanged.

On by default where the image service is local; opt out with images: false.
@changeset-bot

changeset-bot Bot commented Jun 19, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 743d837

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

This PR includes changesets to release 16 packages
Name Type
emdash Minor
@emdash-cms/cloudflare Minor
@emdash-cms/sandbox-workerd Patch
@emdash-cms/fixture-perf-site Patch
@emdash-cms/perf-demo-site Patch
@emdash-cms/cache-demo-site Patch
@emdash-cms/do-demo-site Patch
@emdash-cms/do-solo-demo-site Patch
@emdash-cms/admin Minor
@emdash-cms/auth Minor
@emdash-cms/blocks Minor
@emdash-cms/gutenberg-to-portable-text Minor
@emdash-cms/x402 Minor
create-emdash Minor
@emdash-cms/auth-atproto Patch
@emdash-cms/plugin-embeds 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

Copy link
Copy Markdown
Contributor

Scope check

This PR changes 753 lines across 16 files. Large PRs are harder to review and more likely to be closed without review.

If this scope is intentional, no action needed. A maintainer will review it. If not, please consider splitting this into smaller PRs.

See CONTRIBUTING.md for contribution guidelines.

@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Jun 19, 2026

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Updated (UTC)
✅ Deployment successful!
View logs
docs 743d837 Jun 19 2026, 08:58 PM

@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Jun 19, 2026

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Updated (UTC)
✅ Deployment successful!
View logs
emdash-playground 743d837 Jun 19 2026, 08:58 PM

@pkg-pr-new

pkg-pr-new Bot commented Jun 19, 2026

Copy link
Copy Markdown

Open in StackBlitz

@emdash-cms/admin

npm i https://pkg.pr.new/@emdash-cms/admin@1549

@emdash-cms/auth

npm i https://pkg.pr.new/@emdash-cms/auth@1549

@emdash-cms/auth-atproto

npm i https://pkg.pr.new/@emdash-cms/auth-atproto@1549

@emdash-cms/blocks

npm i https://pkg.pr.new/@emdash-cms/blocks@1549

@emdash-cms/cloudflare

npm i https://pkg.pr.new/@emdash-cms/cloudflare@1549

@emdash-cms/contentful-to-portable-text

npm i https://pkg.pr.new/@emdash-cms/contentful-to-portable-text@1549

emdash

npm i https://pkg.pr.new/emdash@1549

create-emdash

npm i https://pkg.pr.new/create-emdash@1549

@emdash-cms/gutenberg-to-portable-text

npm i https://pkg.pr.new/@emdash-cms/gutenberg-to-portable-text@1549

@emdash-cms/plugin-cli

npm i https://pkg.pr.new/@emdash-cms/plugin-cli@1549

@emdash-cms/plugin-types

npm i https://pkg.pr.new/@emdash-cms/plugin-types@1549

@emdash-cms/registry-client

npm i https://pkg.pr.new/@emdash-cms/registry-client@1549

@emdash-cms/registry-lexicons

npm i https://pkg.pr.new/@emdash-cms/registry-lexicons@1549

@emdash-cms/sandbox-workerd

npm i https://pkg.pr.new/@emdash-cms/sandbox-workerd@1549

@emdash-cms/x402

npm i https://pkg.pr.new/@emdash-cms/x402@1549

@emdash-cms/plugin-ai-moderation

npm i https://pkg.pr.new/@emdash-cms/plugin-ai-moderation@1549

@emdash-cms/plugin-atproto

npm i https://pkg.pr.new/@emdash-cms/plugin-atproto@1549

@emdash-cms/plugin-audit-log

npm i https://pkg.pr.new/@emdash-cms/plugin-audit-log@1549

@emdash-cms/plugin-color

npm i https://pkg.pr.new/@emdash-cms/plugin-color@1549

@emdash-cms/plugin-embeds

npm i https://pkg.pr.new/@emdash-cms/plugin-embeds@1549

@emdash-cms/plugin-field-kit

npm i https://pkg.pr.new/@emdash-cms/plugin-field-kit@1549

@emdash-cms/plugin-forms

npm i https://pkg.pr.new/@emdash-cms/plugin-forms@1549

@emdash-cms/plugin-webhook-notifier

npm i https://pkg.pr.new/@emdash-cms/plugin-webhook-notifier@1549

commit: 743d837

@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Jun 19, 2026

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Updated (UTC)
❌ Deployment failed
View logs
emdash-demo-cache 743d837 Jun 19 2026, 08:58 PM

@ascorbic ascorbic changed the title fix(media): optimize storage-backed images behind Cloudflare Access fix(media): load transformed images from storage, not via HTTP request Jun 19, 2026
@github-actions

Copy link
Copy Markdown
Contributor

Overlapping PRs

This PR modifies files that are also changed by other open PRs:

This may cause merge conflicts or duplicated work. A maintainer will coordinate.

@ascorbic ascorbic added the bot:review Trigger an emdashbot code review on this PR label Jun 20, 2026
@ascorbic ascorbic merged commit a623c6b into main Jun 22, 2026
44 of 45 checks passed
@ascorbic ascorbic deleted the feat/storage-backed-image-optimization branch June 22, 2026 06:49
@emdashbot emdashbot Bot mentioned this pull request Jun 22, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/cloudflare area/core bot:review Trigger an emdashbot code review on this PR overlap review/needs-review No maintainer or bot review yet size/XL

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant