Skip to content

fix(module): derive media list items from keys instead of N+1 fetches - #527

Merged
hendrikheil merged 3 commits into
mainfrom
fix/media-list-n-plus-one
Aug 17, 2026
Merged

fix(module): derive media list items from keys instead of N+1 fetches#527
hendrikheil merged 3 commits into
mainfrom
fix/media-list-n-plus-one

Conversation

@hendrikheil

@hendrikheil hendrikheil commented Jul 2, 2026

Copy link
Copy Markdown
Collaborator

Summary

host.media.list() (src/module/src/runtime/host.ts) called storage.getItem() once per key on top of storage.getKeys(), firing one HTTP request per file whenever publicAssetsStorage is backed by an HTTP driver (dev mode, /__nuxt_studio/dev/public/**).

On a public/ folder with many files, this floods the browser's per-origin connection limit — many requests get stuck in pending and Promise.all never resolves.

Since host.media.list() is awaited inside draftMedias.load() (via the studio:draft:media:updated hook → handleDraftUpdate()), and isReady in useStudio.ts is only set after both draftDocuments.load() and draftMedias.load() resolve, a stuck media listing blocks isReady forever — with no console error, since nothing actually fails, it just never completes.

This silently broke the whole "auto-open on mount" flow in app.vue (and by extension the floating "Edit this page" button, gated on isReady), since #504 made that flow explicitly wait on isReady instead of firing on a bare timeout.

Fix

All fields needed to populate the media tree/browser (id, extension, stem, path, fsPath) are derivable directly from the storage key — no need to fetch each item's content/metadata just to list it. list() now does a single getKeys() call and computes these fields client-side, matching the same derivation already used in the prod build-time template (templates.ts:getAssetsDefaultStorageTemplate) and the dev server route (server/routes/dev/public/[...path].ts).

Verified generateMediaIdFromFsPath() produces identical id strings to those two existing code paths, so this doesn't introduce any id mismatch that could affect draft status detection (getStatus() compares original.id !== modified.id).

Full per-file metadata is still fetched lazily via host.media.get(fsPath) when a specific file is actually opened — only the eager, unbounded fan-out at listing time is removed.

host.media.list() called storage.getItem() per key on top of getKeys(),
firing one HTTP request per file through the dev-mode HTTP storage driver.
On a public/ folder with many files this floods the browser's per-origin
connection limit and never resolves, which blocks isReady (and everything
gated behind it, e.g. the floating "Edit this page" button) forever.

All fields needed for listing (id, extension, stem, path, fsPath) are
derivable from the storage key alone, so list() no longer needs a
per-item round trip. Full metadata is still fetched lazily via
host.media.get() when a specific file is opened.
@vercel

vercel Bot commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
content-studio Ready Ready Preview Aug 3, 2026 8:02am
nuxt.studio Ready Ready Preview Aug 3, 2026 8:02am

@pkg-pr-new

pkg-pr-new Bot commented Jul 2, 2026

Copy link
Copy Markdown
npm i https://pkg.pr.new/nuxt-studio@527

commit: 77c0320

…ix mismatch

list()'s key-parsing assumed every storage key was raw and unprefixed, but
production's pre-baked publicAssetsStorage (templates.ts) stores items under
keys already prefixed with the virtual media collection name, so list() was
double-prefixing id/fsPath/path for every asset in a standard prod deploy.

Extracts mediaItemFieldsFromKey() into utils/media.ts as the single place
that derives id/extension/stem/path/fsPath from a raw key, used by host.ts,
templates.ts, and the dev public route. Also fixes the pre-existing stem
no-op (split('.').join('.') simply reconstructs the input) in all three.
- mediaItemFieldsFromKey: compute stem from fsPath instead of the
  colon-joined key, matching the existing pattern in
  medias/[...path].ts, so nested paths don't leak colons into stem
- host.ts: hoist the collection-prefix RegExp out of the per-key
  map() loop
- add unit tests for mediaItemFieldsFromKey
@hendrikheil
hendrikheil marked this pull request as ready for review August 10, 2026 09:11
@hendrikheil
hendrikheil merged commit 5e78555 into main Aug 17, 2026
7 checks passed
@hendrikheil
hendrikheil deleted the fix/media-list-n-plus-one branch August 17, 2026 08:04
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.

2 participants