Code change:
<!-- Please provide a clear and concise description of what the bug is. Include screenshots if needed. Please test using the latest version of the relevant React packages to make sure your issue has not already been fixed. --React version: 18.3.0-canary-- SSR app that has images in its tree using renderToString or renderToPipeableStream <!-- Your bug will get fixed much faster if we can run your code and it doesn't have dependencies other than React. Issues without reproduction steps or code examples may be immediately closed as not actionable. --Link to code example: <!-- Please provide a CodeSandbox (), a link to a repository on GitHub, or provide a minimal code example that reproduces the problem. You may provide a screenshot of the application if you think it is relevant to your bug report. Here are some tips for providing a minimal example: --! To not insert preloads randomly. Note that there is difference between renderToString and renderToPipeableStream, renderToString the preloads appear inside root and renderToPipeableStream they appear in head. Sorry this is a bit of a question rather than bug report maybe, I have searched around and cannot see any mention of auto-preloading images but I assume there must be something going on that I have missed. I was able to fix by using .
This. Tested with . It would be "kinda" tolerable for the LCP image above fold (featured image or similar), but we have lots of lazy loaded images below fold which contain <noscriptalternatives like: For every tag used in noscript, the image is preloaded in which completely defies the purpose of lazy loading.
I think this is actually now expected behaviour, after digging the source a bit I found Though I could not find any docs/release notes that mention this which is a little odd as now you need to explicitly think about the loading strategy of every img tag you use. For me I was able to "fix" this by making sure all our img tags have either loading=lazy or fetchPriority=low when they are not to be preloaded, this gave me more or less only the LCP images preloaded. For your case you could either add loading=lazy or fetchPriority=low to the fallback images, though depending on your target browsers you could get rid of the noscript parts these days as loading=lazy has pretty wide browser support now. Would be good to have this documented somewhere and also maybe add this to the react linter.
Thanks for the info regarding loading=lazy, that could solve it. If this is really a "feature", I have to say, I don't think it should be React's business to put stuff into my automagically…
Which framework are you using?
For me, its Remix.
You're probably using their image component that uses . Or they're doing calls some other way. Can you open an issue on their issue tracker first to clarify?
I'm not using their image component but , but I will take a look at Remix & React Router code, whether they call this somewhere and open an issue. Interestingly, this behavior is only applied by Remix (or happens for some reason), when upgrading to React canary.
React is adding support for Suspensey "resource". For instance if you render a stylesheet and opt into React's handling of this using "precedence" then commits which contain this stylesheet will wait to commit until the sheet has loaded. Another area that we're making Suspensey is images. Lazy images indicate that they do not require suspense coordination but by default images will (in the future) block a boundary reveal until the image has loaded and been decoded. While these features are for client updates there are analogs for SSR. for stylesheets browsers already block paint until stylesheets load in the head. They don't however do this for images. React canaries now preload the first few images rendered if they are not lazy because we want to mimic the coordinated reveal of Suspensey images along with the first paint. It's not a perfect heuristic because it's not actually enforced to be coordinated but it does better align the behavior across suspensy vs lazy images. Another way you can signal to React that an image is not expected to be part of the first paint is to set the fetchPriority to "low"
For every <imgtag used in noscript, the image is preloaded in <headwhich completely defies the purpose of lazy loading. This is a genuine bug. will land a fix
Good to know, thanks for the explanation.
fwiw no framework used just renderToPipeableStream, in my case I was able to add low priority to a few icon images to get the preload to load reasonable candidates. Would be good to be able to opt-out of this somehow for a boundary, thinking of the case where you are waiting on component/data to load and don't really care about the image but lazy loading would be inappropriate for an above the fold larger image. Maybe even a way to modify the heuristic via some sort of callback would be useful.

Related discussion:
text: props, resumableState, renderState, <del> !!(formatContext.tagScope & PICTURE_SCOPE), </del> <ins> !!(formatContext.tagScope & (PICTURE_SCOPE | NOSCRIPT_SCOPE)), </ins> ); } // Omitted close tags