dotCMS / dotCMS/core

UVE: contentlet image flashes on click when consumer uses next/image

Open
#35,540 1 comment 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

stale
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/image whose 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:

  1. SDK's capture-phase click handler emits SET_SELECTED_CONTENTLET correctly.
  2. Editor handles it (dot-uve-actions-handler.service.ts:106) and sets editorSelectedContentletArea + editorActiveContentlet. ✅ Selection is correct in the store.
  3. $handleReloadContentEffect in edit-ema-editor.component.ts:410 re-fires on the click, calling reloadIframeContent() → posts UVE_SET_PAGE_DATA to the iframe.
  4. The consumer's useEditableDotCMSPage (libs/sdk/react/.../useEditableDotCMSPage.ts:137) handles CONTENT_CHANGES and replaces the entire page response via setUpdatedPageResponse(payload).
  5. 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() returns pageAsset.clientResponse falls back to {params}.
  • The two-stage emit suggests the editor sends an interim payload before the graphql resolver finishes the image relations. Check whether clientResponse is being read while still partially populated.

Reproduction (manual)

  1. Run the dotCMS Angular admin (yarn nx serve dotcms-ui) and the example Next.js app (cd core/examples/nextjs && npm run dev).
  2. Open the UVE editor on the demo home page.
  3. Click any Banner or Activity card whose image uses next/image fill — image flashes briefly. (Most reliable on contentlets with data-dot-on-number-of-pages=\"1\".)
  4. Add a render log inside Banner.js / Activity.js:
    console.log('render', JSON.stringify({ title, imageIdentifier: image?.identifier ?? null }));
    
    Each click produces two re-renders for the affected contentlet:
    • first with imageIdentifier: \"\"
    • second with the correct identifier ~50ms later.

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

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.