Skip to content

fix(publish): prefetch the asset behind a bound image - #324

Merged
DavidBabinec merged 1 commit into
mainfrom
fix/scalar-entry-media-prefetch
Aug 1, 2026
Merged

fix(publish): prefetch the asset behind a bound image#324
DavidBabinec merged 1 commit into
mainfrom
fix/scalar-entry-media-prefetch

Conversation

@DavidBabinec

Copy link
Copy Markdown
Contributor

What breaks

<img src="{currentEntry.featuredMedia}"> — one image per entry, on every entry route and in every loop row, the ordinary CMS pattern — publishes as:

<img src="/uploads/xxx-aid-bte.webp" alt="" loading="lazy" decoding="async">

No alt. No srcset. No width/height. A statically-authored image on the same page gets all three.

Why

The node prop holds the binding token, not a path, so collectMediaPaths skips it — it only recognises a literal /uploads/.... The resolved path lives on the entry instead, and collectEntryArrayReferences only collected a string when it sat inside an array:

if (typeof value === 'string') {
  if (insideArray && value) references.add(value)   // scalar → dropped
  return
}

A multi-media cell (gallery: ['id-1', 'id-2']) resolved. A scalar featuredMedia: '/uploads/aid.webp' did not.

With no asset in the map, props._resolvedMediaByKey.src is undefined, and the image module reads alt exclusively from the library asset — deliberately, per its own comment:

Alt text comes exclusively from the library asset — the library is the single source of truth for accessibility metadata. Edited in the Media viewer (asset row), never as a per-instance module prop.

That is a reasonable rule, but combined with the missing prefetch it means there is no way to give a bound image alt text at all. Setting alt text on the asset does nothing (never fetched); authoring alt="..." in the HTML does nothing (ignored by design). Every bound image is unlabelled, and the same lookup supplies srcset, sizes, width and height — so phones get the full-size original and each image shifts layout on load.

Found while building a template pack: six entry pages, each with its own photograph, all published unlabelled with the alt text sitting correctly in the media library.

The fix

Collect scalar strings too, but only when they are an upload path:

if (value && (insideArray || value.startsWith('/uploads/'))) references.add(value)

That picks out featuredMedia / featuredMediaPath exactly — the loop source stores the resolved public URL there, not an id — while keeping an entry's prose fields out of the batched IN list. Collecting every scalar would drag the whole text surface of every row into the query.

Renamed to collectEntryMediaReferences, since it is no longer about arrays.

Tests

Three added to mediaBatchResolution.test.ts. Two fail without the fix:

  • a scalar entry media path resolves (the bound featuredMedia case)
  • a scalar media path carried by a loop row resolves
  • an ordinary scalar text field is not treated as a reference — the guard test: its value is deliberately set to a string that matches a real asset id, and must still not resolve, because scalars qualify by being a /uploads/ path and never by looking like an id

Full suite 6515 pass / 0 fail, bun run lint and tsc -b clean.

🤖 Generated with Claude Code

`<img src="{currentEntry.featuredMedia}">` is the ordinary CMS pattern —
one image per entry, on every entry route and in every loop row — and it
published with an empty alt, no srcset and no intrinsic dimensions.

The node prop holds the binding token, not a path, so `collectMediaPaths`
never sees it. The resolved `/uploads/...` path is on the entry, and
`collectEntryArrayReferences` collected strings only when they sat inside
an ARRAY: multi-media cells resolved, a scalar `featuredMedia` did not.

With no asset, `props._resolvedMediaByKey.src` is undefined, and the
image module reads alt exclusively from the library asset — by design,
"the library is the single source of truth for accessibility metadata".
So the alt is unreachable and an authored `alt` attribute is ignored:
there is no way to give a bound image alt text at all. Same lookup also
supplies srcset, sizes, width and height, so phones were served the
full-size original and every such image shifted layout on load.

Collect scalar strings too, but only when they are an upload path. That
picks out `featuredMedia` / `featuredMediaPath` exactly, and keeps an
entry's prose out of the IN-list. Renamed to collectEntryMediaReferences,
since it is no longer about arrays.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@DavidBabinec
DavidBabinec merged commit c6481c0 into main Aug 1, 2026
9 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant