Skip to content

fix: support onLoadingComplete in modern next/image shim#496

Merged
james-elicx merged 2 commits intocloudflare:mainfrom
NathanDrake2406:fix/image-onloadingcomplete
Mar 12, 2026
Merged

fix: support onLoadingComplete in modern next/image shim#496
james-elicx merged 2 commits intocloudflare:mainfrom
NathanDrake2406:fix/image-onloadingcomplete

Conversation

@NathanDrake2406
Copy link
Copy Markdown
Contributor

Summary

  • Adds the deprecated-but-still-supported onLoadingComplete prop to the modern next/image shim
  • Callback receives HTMLImageElement (matching Next.js modern image behavior, not the { naturalWidth, naturalHeight } form from legacy image)
  • Wired into all three render paths: local images, custom loader, and remote images via UnpicImage
  • getImageProps destructures both onLoad and onLoadingComplete so neither leaks into returned props

Closes #15

Test plan

  • 4 new tests in tests/image-component.test.ts covering prop leak prevention via getImageProps and SSR output for local + custom loader paths
  • All 33 image component tests pass
  • All 661 shim tests pass
  • Typecheck clean
  • CI: Format, Lint, Typecheck, Vitest, Playwright E2E

Add the deprecated-but-still-supported onLoadingComplete prop to the
modern next/image component, matching Next.js behavior where the
callback receives the underlying HTMLImageElement on load.

- Add onLoadingComplete to ImageProps interface and type declarations
- Wire handleLoad into all render paths (local, custom loader, remote)
- Destructure onLoad/onLoadingComplete in getImageProps to prevent leak
@chatgpt-codex-connector
Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, you can upgrade your account or add credits to your account and enable them for code reviews in your settings.

@pkg-pr-new
Copy link
Copy Markdown

pkg-pr-new Bot commented Mar 12, 2026

Open in StackBlitz

npm i https://pkg.pr.new/vinext@496

commit: 820ed54

@james-elicx
Copy link
Copy Markdown
Collaborator

/bigbonk review

Copy link
Copy Markdown
Contributor

@ask-bonk ask-bonk Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review: onLoadingComplete support in modern next/image shim

Good feature addition — the implementation covers all render paths (local, custom loader, remote/UnpicImage) and correctly destructures the prop in getImageProps to prevent leaking. The tests are well-targeted. Two issues to address:

1. Callback invocation order is reversed vs Next.js (bug)

In Next.js's modern next/image, when both onLoad and onLoadingComplete are provided, onLoad fires first, then onLoadingComplete. This PR fires them in the opposite order. This is a behavioral difference that could break code relying on side effects in onLoad completing before onLoadingComplete runs.

2. Stale comment describes legacy behavior, not modern

The comment says "Calls the callback with { naturalWidth, naturalHeight }" but the code actually passes HTMLImageElement (which is correct for modern next/image). The comment describes the legacy image behavior.

Test coverage

The tests are solid for prop leak prevention and SSR attribute sanitization. One gap: there's no test for the remote image (UnpicImage) SSR path — the tests only cover local images and custom loaders. A remote URL test would complete the coverage for all three render paths.

Overall this is a clean, well-scoped change. Fix the invocation order and the comment, and it's good to go.

Comment on lines +213 to +216
const handleLoad = onLoadingComplete
? (e: React.SyntheticEvent<HTMLImageElement>) => {
onLoadingComplete(e.currentTarget);
onLoad?.(e);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: invocation order reversed vs Next.js. In the upstream modern next/image (image-component.tsx), when both callbacks are present, onLoad fires before onLoadingComplete. This PR fires onLoadingComplete first.

This matters because user code may rely on onLoad side effects completing before onLoadingComplete runs.

Suggested change
const handleLoad = onLoadingComplete
? (e: React.SyntheticEvent<HTMLImageElement>) => {
onLoadingComplete(e.currentTarget);
onLoad?.(e);
const handleLoad = onLoadingComplete
? (e: React.SyntheticEvent<HTMLImageElement>) => {
onLoad?.(e);
onLoadingComplete(e.currentTarget);
}
: onLoad;

Note: the legacy image shim correctly fires onLoadingComplete first (matching legacy Next.js behavior), so only the modern shim needs this fix.

Comment thread packages/vinext/src/shims/image.tsx Outdated
Comment on lines +211 to +212
// Wire onLoadingComplete (deprecated) into onLoad — matches Next.js behavior.
// Calls the callback with { naturalWidth, naturalHeight } after the image loads.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The comment says "Calls the callback with { naturalWidth, naturalHeight }" but the code passes HTMLImageElement (line 215). The { naturalWidth, naturalHeight } form is the legacy next/legacy/image signature. The modern next/image signature (which this shim implements) passes the raw HTMLImageElement.

Suggested change
// Wire onLoadingComplete (deprecated) into onLoad — matches Next.js behavior.
// Calls the callback with { naturalWidth, naturalHeight } after the image loads.
// Wire onLoadingComplete (deprecated) into onLoad — matches Next.js behavior.
// Calls the callback with the HTMLImageElement after the image loads.

@ask-bonk
Copy link
Copy Markdown
Contributor

ask-bonk Bot commented Mar 12, 2026

Review posted successfully. Here's a summary of the feedback:

Two issues found:

  1. Bug: Callback invocation order is reversed. In upstream Next.js's modern next/image, when both onLoad and onLoadingComplete are provided, onLoad fires first, then onLoadingComplete. This PR fires them in the opposite order (onLoadingComplete first, then onLoad). Posted a suggestion to swap the two lines.

  2. Stale comment. The comment on line 212 says "Calls the callback with { naturalWidth, naturalHeight }" but the code correctly passes HTMLImageElement. The { naturalWidth, naturalHeight } form is the legacy image signature, not the modern one. Posted a suggestion to fix the comment.

I also noted that test coverage could be improved with a remote URL (UnpicImage) SSR test to complete coverage across all three render paths, though the existing tests for prop leak prevention and local/custom-loader SSR are solid.

github run

- Swap onLoad/onLoadingComplete invocation order to match Next.js
  (onLoad fires first, then onLoadingComplete)
- Fix stale comment that described legacy { naturalWidth, naturalHeight }
- Add remote URL (UnpicImage) SSR test for full path coverage
@james-elicx
Copy link
Copy Markdown
Collaborator

/bigbonk re-review

Copy link
Copy Markdown
Contributor

@ask-bonk ask-bonk Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Clean, well-scoped change. The implementation correctly handles all three render paths (local, custom loader, remote/UnpicImage), the callback order matches Next.js (onLoad before onLoadingComplete), and both props are properly stripped in getImageProps.

The second commit addressed the earlier review feedback (callback order swap, stale comment fix, remote URL test). All 34 image component tests pass.

One minor note below, but nothing blocking.

className,
style,
onLoad: _onLoad,
onLoadingComplete: _onLoadingComplete,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: getImageProps previously passed onLoad through to the returned props via ...rest (since it wasn't destructured). This PR now strips it by destructuring as _onLoad. That's actually the correct behavior — Next.js's getImageProps also strips event handlers from the output — but it's worth noting this is a subtle secondary fix bundled into the PR.

@ask-bonk
Copy link
Copy Markdown
Contributor

ask-bonk Bot commented Mar 12, 2026

Review posted. Here's a summary of my findings:

Approved — this is a clean, well-scoped change.

What the PR does:

  • Adds the deprecated-but-still-supported onLoadingComplete prop to the modern next/image shim
  • Wires it into all three render paths (local images, custom loader, remote/UnpicImage)
  • Strips both onLoad and onLoadingComplete from getImageProps output

What I verified:

  1. The callback order is correct in the current code — onLoad fires first, then onLoadingComplete (fixed in the second commit after earlier review feedback)
  2. The comment at line 212 now correctly describes the modern behavior (HTMLImageElement, not { naturalWidth, naturalHeight })
  3. onLoadingComplete is properly destructured and consumed in both the Image component and getImageProps, so it never leaks to the DOM
  4. The remote URL SSR test was added in the second commit, covering all three render paths
  5. All 34 image component tests pass
  6. One subtle secondary fix: onLoad is now also stripped from getImageProps output (previously it leaked via ...rest), which actually matches Next.js behavior

github run

@james-elicx james-elicx merged commit eaade0f into cloudflare:main Mar 12, 2026
18 checks passed
@NathanDrake2406 NathanDrake2406 deleted the fix/image-onloadingcomplete branch March 18, 2026 09:50
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