UVE: contentlet image flashes on click when consumer uses next/image
Nobody has claimed this yet.
- Dominant language
- Java
- Stars
- 970
- Forks
- 486
- Avg merge
- 3d 33m
- Merged PRs (30d)
- 170
Description
Summary
Clicking a contentlet inside the UVE iframe causes its <Image> (next/image) to flash — disappear for one render frame, then reappear. Affects React/Next.js consumers using useEditableDotCMSPage + next/image fill. Reproduces consistently on certain contentlets, but the root cause is general.
Where it happens
- Repro app:
core/examples/nextjs/ - Affected components: any consumer component using
next/imagewhose image is bound to a field resolved on the editor's clientResponse (e.g.Banner,Activity). - Trigger: clicking the contentlet (selection) — no edit, no save.
What we know
We instrumented the path with logs and found:
- SDK's capture-phase
clickhandler emitsSET_SELECTED_CONTENTLETcorrectly. - Editor handles it (
dot-uve-actions-handler.service.ts:106) and setseditorSelectedContentletArea+editorActiveContentlet. ✅ Selection is correct in the store. $handleReloadContentEffectinedit-ema-editor.component.ts:410re-fires on the click, callingreloadIframeContent()→ postsUVE_SET_PAGE_DATAto the iframe.- The consumer's
useEditableDotCMSPage(libs/sdk/react/.../useEditableDotCMSPage.ts:137) handlesCONTENT_CHANGESand replaces the entire page response viasetUpdatedPageResponse(payload). - The first replacement payload has
image.identifier === \"\"for affected contentlets. A second replacement ~50–60ms later has the correct identifier. This produces:<Image src=\"\" />(one frame) → image disappears<Image src=\"<correct>\" />→ image reappears
The empty-identifier first payload is the actual flash mechanism.
Why click triggers reload
Confirmed via the stack trace from reloadIframeContent:
at $handleReloadContentEffect.ngDevMode.debugName
at runEffect
at runEffectsInView
at refreshView
$reloadEditorContent is a computed that reads pageAsset.page.rendered, pageType, editorEnableInlineEdit(). None of those values change on click. But Angular signals still propagate version bumps from upstream producers; the click writes to editorSelectedContentletArea and editorActiveContentlet, which (somehow) cycles through enough graph state to make the effect re-evaluate.
Mitigation already in place (PR #35539 / commit 96b859dee4): added a structural equal to $reloadEditorContent so the effect only re-fires when the values actually differ. This stops the click-triggered refire. But:
- If the deps genuinely change (real edit, real save), the same flash reproduces.
- The underlying empty-identifier-first-payload bug remains.
Where to look
The flash mechanism is whichever code path produces the first clientResponse with image.identifier === \"\". Likely candidates:
libs/portlets/edit-ema/portlet/src/lib/store/features/page-api/withPageApi.ts— graphql clientResponse construction (especially around contentlet → image resolution).libs/portlets/edit-ema/portlet/src/lib/edit-ema-editor/edit-ema-editor.component.ts:1495—#clientPayload()returnspageAsset.clientResponsefalls back to{params}.- The two-stage emit suggests the editor sends an interim payload before the graphql resolver finishes the image relations. Check whether
clientResponseis being read while still partially populated.
Reproduction (manual)
- Run the dotCMS Angular admin (
yarn nx serve dotcms-ui) and the example Next.js app (cd core/examples/nextjs && npm run dev). - Open the UVE editor on the demo home page.
- Click any Banner or Activity card whose image uses
next/image fill— image flashes briefly. (Most reliable on contentlets withdata-dot-on-number-of-pages=\"1\".) - Add a render log inside
Banner.js/Activity.js:
Each click produces two re-renders for the affected contentlet:console.log('render', JSON.stringify({ title, imageIdentifier: image?.identifier ?? null }));- first with
imageIdentifier: \"\" - second with the correct identifier ~50ms later.
- first with
Suspected scope
Pre-existing — not caused by the iframe-sizing refactor (PR #35539). Surfaced because the new SDK click→select path made click a load-bearing user gesture that runs the reload pipeline, where previously the editor's hover-overlay caught most clicks before the iframe saw them.
Workaround
Consumer-side: cache the last non-empty image.identifier per contentlet in the React component, so an empty value doesn't replace a known-good src. Not ideal, but unblocks until the editor stops emitting the empty-first payload.
Related
- PR #35539 (`issue-35514-uve-iframe-sizing-phase-1`)
- Closes/relates: #35514
🤖 Investigation captured by Claude Code.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Reproduce the flash with core/examples/nextjs/ and inspect useEditableDotCMSPage.ts:137, withPageApi.ts, and edit-ema-editor.component.ts:1495. Trace why the first clientResponse has an empty image.identifier before the corrected payload arrives. Done when selection or genuine reloads no longer emit an empty-first image response and the Banner/Activity reproduction no longer flashes.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- angular, graphql, next.js, react
- Domain
- api, frontend
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 45/100