Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[React 19] Different behaviors with preload method #30056

Open
ahce opened this issue Jun 23, 2024 · 4 comments
Open

[React 19] Different behaviors with preload method #30056

ahce opened this issue Jun 23, 2024 · 4 comments

Comments

@ahce
Copy link

ahce commented Jun 23, 2024

Summary

  • react & react-dom version: 19.0.0-rc-3563387fe3-20240621
  • node version: 20.14.0

When using react's preload method, the preload link tag is not created in certain cases.
It works correctly with two link tags (OnlyLinkTags component).

Code example

const fs = require('node:fs');
const { createElement } = require('react');
const { preload, preinit } = require('react-dom');
const { renderToString } = require('react-dom/server');

const CSS = '/some.css';
const LINK_PROPS = {
  rel: 'stylesheet',
  href: CSS,
};

/**
 * Doesn't create the preload link tag
 */
const PreloadAndPreinit = () => {
  preload(CSS, { as: 'style' });
  preinit(CSS, { as: 'style' });

  return null;
};

/**
 * Doesn't create the preload link tag
 */
const PreloadAndLinkWithPrecedence = () => {
  preload(CSS, { as: 'style' });

  return createElement('link', { ...LINK_PROPS, precedence: 'custom' });
};

/**
 * Creates the preload link tag correctly into the head
 * The stylesheet link tag stays in the body, because it doesn't have precedence
 */
const PreloadAndLinkWithoutPrecedence = () => {
  preload(CSS, { as: 'style' });

  return createElement('link', LINK_PROPS);
};

/**
 * Moves both tags into the head
 */
const OnlyLinkTags = () => [
  createElement('link', {
    key: 'preload',
    rel: 'preload',
    as: 'style',
    href: CSS,
  }),
  createElement('link', { ...LINK_PROPS, key: 'link', precedence: 'custom' }),
];

[
  PreloadAndPreinit,
  PreloadAndLinkWithPrecedence,
  PreloadAndLinkWithoutPrecedence,
  OnlyLinkTags,
].forEach((Component) => {
  fs.writeFileSync(
    `_${Component.name}.html`,
    renderToString(
      createElement(
        'html',
        null,
        createElement('head'),
        createElement('body', null, createElement(Component)),
      ),
    ),
  );
});

Outputs:

PreloadAndPreinit:

preload("/some.css", { as: 'style' });
preinit("/some.css", { as: 'style' });

Current:

<html>
  <head>
    <link rel="stylesheet" href="/some.css" data-precedence="default" />
  </head>
  <body></body>
</html>

Expected:

<html>
  <head>
    <link rel="stylesheet" href="/some.css" data-precedence="default" />
    <link rel="preload" as="style" href="/some.css" />
  </head>
  <body></body>
</html>

PreloadAndLinkWithPrecedence:

preload("/some.css", { as: 'style' });

<link rel="stylesheet" href="/some.css" precedence="custom" />

Current:

<html>
  <head>
    <link rel="stylesheet" href="/some.css" data-precedence="custom" />
  </head>
  <body></body>
</html>

Expected:

<html>
  <head>
    <link rel="stylesheet" href="/some.css" data-precedence="default" />
    <link rel="preload" as="style" href="/some.css" />
  </head>
  <body></body>
</html>

PreloadAndLinkWithoutPrecedence:

preload("/some.css", { as: 'style' });

<link rel="stylesheet" href="/some.css" />

Current:

<html>
  <head>
    <link rel="preload" href="/some.css" as="style" />
  </head>
  <body>
    <link rel="stylesheet" href="/some.css" />
  </body>
</html>

Expected:

The same as current.

OnlyLinkTags:

<link rel="preload" as="style" href="/some.css" />
<link rel="stylesheet" href="/some.css" />

Current:

<html>
  <head>
    <link rel="stylesheet" href="/some.css" data-precedence="custom" />
    <link rel="preload" as="style" href="/some.css" />
  </head>
  <body></body>
</html>

Expected:

The same as current.

@ahce ahce added the React 19 label Jun 23, 2024
@eps1lon
Copy link
Collaborator

eps1lon commented Jun 25, 2024

Thank you for the details. Could you include the expected behavior for each case? It's not clear at a glance which cases work as expected and which don't.

@ahce
Copy link
Author

ahce commented Jun 25, 2024

Thank you for the details. Could you include the expected behavior for each case? It's not clear at a glance which cases work as expected and which don't.

Added expected behavior! 😃

Copy link

This issue has been automatically marked as stale. If this issue is still affecting you, please leave any comment (for example, "bump"), and we'll keep it open. We are sorry that we haven't been able to prioritize it yet. If you have any new additional information, please include it with your comment!

@github-actions github-actions bot added the Resolution: Stale Automatically closed due to inactivity label Sep 23, 2024
@eps1lon eps1lon added Type: Discussion and removed Resolution: Needs More Information Resolution: Stale Automatically closed due to inactivity labels Sep 24, 2024
@gnoff
Copy link
Collaborator

gnoff commented Sep 24, 2024

hi @ahce, the preload functions aren't a shortcut to rendering a preload tag. They are an instruction to react to inform it about a resource that will be used in the future that we can load early if we have the opportunity. When you call preload and then preinit React understands that the preload is for the same resource that the preinit is for and it chooses to emit the stylesheet itself rather than a preload of the stylesheet followed by the stylesheet. I'm not sure what your goal is with the preload tag + stylesheet tag but functionally the browser won't use it and it just increases the size of the intial HTML payload. Another reason React doesn't emit both is preloads don't always have the best prioritization. A preload link can get in the browsers way of optimizing which resources to fetch in what priority. Now in this case stylesheets have very high priority anyway so its sort of irrelevant but preloads for scripts for instance would likely accidentally bump up their priority relative to a script tag so if we're going to emit the script itself in the current flush we don't preload it too.

The reason you see both tags when you render a regular stylesheet is that without precedence the stylehseet link tag is just considered an normal tag with no hoisting or special behavior. There is no understanding in react that the link tag is related to the requested preload. in this case those tags are side by side but in a real app it's entirely possible the stylesheet is very deep in the document (since React is not going to control it's location by hoisting it up to the head) and so we assume we still need/want the preload b/c it will arrive much sooner in document order.

So to sum up. the observe behavior is intended. A call to preload is an instruction not a directive, and React uses heuristics to optimize resource loading and in the cases you outlined the stylesheet is available before we could send the preload so we omit it to save on document byte size and to avoid mis-prioritizing the resource fetch.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants