@samitouri / QOS-React / commits / 9b042f9d59

[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(),