[Fizz] Responsive images should not be preloaded with link headers (#32445)
Link headers are optionally supported for cases where you prefer to send resource loading hints before you're ready to send the body of a request. While many resources can be correctly preloaded from a link header responsive images are currently not supported and end up preloading the default src rather than the correctly sized image. Until responsive images are supported React will not allow these images to preload as headers and will retain them to preload as HTML. closes: #32437
Josh Story committed
Feb 21, 2025 at 09:48 UTC
9b042f9d593f965d8c7a42f8f5fce322f403381b
2 files changed
+34
-7
packages/react-dom-bindings/src/server/ReactFizzConfigDOM.js
+7
@@ -2958,6 +2958,9 @@ function pushImg(
2958
if (
2959
headers &&
2960
headers.remainingCapacity > 0 &&
2961
+ // browsers today don't support preloading responsive images from link headers so we bail out
2962
+ // if the img has srcset defined
2963
+ typeof props.srcSet !== 'string' &&
2964
// this is a hueristic similar to capping element preloads to 10 unless explicitly
2965
// fetchPriority="high". We use length here which means it will fit fewer images when
2966
// the urls are long and more when short. arguably byte size is a better hueristic because
@@ -5703,6 +5706,10 @@ function preload(href: string, as: string, options?: ?PreloadImplOptions) {
5706
if (
5707
headers &&
5708
headers.remainingCapacity > 0 &&
5709
+ // browsers today don't support preloading responsive images from link headers so we bail out
5710
+ // if the img has srcset defined
5711
+ typeof imageSrcSet !== 'string' &&
5712
+ // We only include high priority images in the link header
5713
fetchPriority === 'high' &&
5714
// Compute the header since we might be able to fit it in the max length
5715
((header = getPreloadAsHeader(href, as, options)),
packages/react-dom/src/__tests__/ReactDOMFizzServer-test.js
+27
-7
@@ -3723,23 +3723,43 @@ describe('ReactDOMFizzServer', () => {
3723
});
3724
});
3725
3726
- it('encodes img srcset and sizes into preload header params', async () => {
3726
+ it('omits images from preload headers if they contain srcset and sizes', async () => {
3727
let headers = null;
3728
function onHeaders(x) {
3729
headers = x;
3730
}
3731
3732
function App() {
3733
- ReactDOM.preload('presrc', {
3733
+ ReactDOM.preload('responsive-preload-set-only', {
3734
+ as: 'image',
3735
+ fetchPriority: 'high',
3736
+ imageSrcSet: 'srcset',
3737
+ });
3738
+ ReactDOM.preload('responsive-preload', {
3739
+ as: 'image',
3740
+ fetchPriority: 'high',
3741
+ imageSrcSet: 'srcset',
3742
+ imageSizes: 'sizes',
3743
+ });
3744
+ ReactDOM.preload('non-responsive-preload', {
3745
as: 'image',
3746
fetchPriority: 'high',
3736
- imageSrcSet: 'presrcset',
3737
- imageSizes: 'presizes',
3747
});
3748
return (
3749
<html>
3750
<body>
3742
- <img src="src" srcSet="srcset" sizes="sizes" />
3751
+ <img
3752
+ src="responsive-img-set-only"
3753
+ fetchPriority="high"
3754
+ srcSet="srcset"
3755
+ />
3756
+ <img
3757
+ src="responsive-img"
3758
+ fetchPriority="high"
3759
+ srcSet="srcset"
3760
+ sizes="sizes"
3761
+ />
3762
+ <img src="non-responsive-img" fetchPriority="high" />
3763
</body>
3764
</html>
3765
);
@@ -3751,8 +3771,8 @@ describe('ReactDOMFizzServer', () => {
3771
3772
expect(headers).toEqual({
3773
Link: `
3754
-<presrc>; rel=preload; as="image"; fetchpriority="high"; imagesrcset="presrcset"; imagesizes="presizes",
3755
- <src>; rel=preload; as="image"; imagesrcset="srcset"; imagesizes="sizes"
3774
+<non-responsive-preload>; rel=preload; as="image"; fetchpriority="high",
3775
+<non-responsive-img>; rel=preload; as="image"; fetchpriority="high"
3776
`
3777
.replaceAll('\n', '')
3778
.trim(),