Bug report
Current behavior
Avatar.Fallback produces a visible flash during SSR hydration even when the image source is already in the browser cache.
useImageLoadingStatus has no synchronous cached-image fast-path. It always initializes as 'idle' and the only path to 'loaded' is the async onload callback. Since useIsoLayoutEffect is a no-op on the server, the fallback is baked into the server HTML with no Avatar <img> at all. After hydration, the layout effect fires but never checks image.complete, which is already true synchronously for cached images.
https://github.com/mui/base-ui/blob/master/packages/react/src/avatar/image/useImageLoadingStatus.ts#L17

Left: base-ui Avatar shows red fallback. Right: plain <img> with the same cached URL renders instantly.
Measurements with 4x CPU throttle on a minimal page (image shown above):
| Framework |
Red fallback frames |
Flash duration |
| TanStack Start |
14 |
~304ms |
| Next.js |
3 |
~52ms |
| Vite SPA (client-only) |
1 |
~16ms |
The client-side case is a single-frame flash but the SSR case is the real problem. The flash duration scales with hydration time and gets worse on slower devices or larger pages.
Expected behavior
For cached images, Avatar.Image should render immediately without showing Avatar.Fallback. A plain <img> with the same cached URL renders instantly on first paint.
Reproducible example
Any Avatar with a cached src and a visible fallback during SSR hydration. Tested with both Next.js and TanStack Start.
Base UI version
v1.3.0
Which browser are you using?
Chrome, Firefox
Which OS are you using?
Windows, MacOS, Linux
Additional context
Radix Avatar handles this with an image.complete check in the useState initializer to skip the fallback on the first render for cached images: radix-ui/primitives
Happy to open a PR that addresses this.
Bug report
Current behavior
Avatar.Fallbackproduces a visible flash during SSR hydration even when the image source is already in the browser cache.useImageLoadingStatushas no synchronous cached-image fast-path. It always initializes as'idle'and the only path to'loaded'is the asynconloadcallback. SinceuseIsoLayoutEffectis a no-op on the server, the fallback is baked into the server HTML with no Avatar<img>at all. After hydration, the layout effect fires but never checksimage.complete, which is alreadytruesynchronously for cached images.https://github.com/mui/base-ui/blob/master/packages/react/src/avatar/image/useImageLoadingStatus.ts#L17
Left: base-ui Avatar shows red fallback. Right: plain
<img>with the same cached URL renders instantly.Measurements with 4x CPU throttle on a minimal page (image shown above):
The client-side case is a single-frame flash but the SSR case is the real problem. The flash duration scales with hydration time and gets worse on slower devices or larger pages.
Expected behavior
For cached images,
Avatar.Imageshould render immediately without showingAvatar.Fallback. A plain<img>with the same cached URL renders instantly on first paint.Reproducible example
Any
Avatarwith a cachedsrcand a visible fallback during SSR hydration. Tested with both Next.js and TanStack Start.Base UI version
v1.3.0
Which browser are you using?
Chrome, Firefox
Which OS are you using?
Windows, MacOS, Linux
Additional context
Radix Avatar handles this with an
image.completecheck in theuseStateinitializer to skip the fallback on the first render for cached images: radix-ui/primitivesHappy to open a PR that addresses this.