Besides many tricks that improve the performance from within a React app, there are also tricks to improve the loading speed from the HTML document in which the React app is embedded. In this post, we will walk through some of these tricks.

For the impatient, feel free to jump to the conclusion.

Preload Assets Used by the App

If the app uses assets, such as images or fonts, the browser won’t know it needs to load the assets until finishing loading up the app’s JavaScript file or relevant style sheets. Instructing the browser to preload these assets likely would improve the loading speed of the app by allowing the browser to download the assets in parallel to other tasks. For example, the browser would know it can download the assets while parsing the JavaScript file.

Assets with Known URLs

To preload an asset with a known URL, whether from the same origin or not, we can add the URL to a <link rel="preload" .../> block to the head section, or use the equivalent HTTP Link entity-header field. This informs the browser that the page needs that asset and that the browser can start downloading it whenever it deems appropriate. For example, if the app uses an image located at https://example.com/my-image.png, we can add the following to the head section:

<link rel="preload" href="https://example.com/my-image.png" as="image" type="image/png" />

It is usually safer to add it before the script tag that loads the app, because the script tag may block HTML parsing depending on how the HTML file loads it.

Experimental Comparison

Consider the following app that loads an image:

function App() {
  return <img src="https://picsum.photos/id/0/200" />;
}

ReactDOM.createRoot(document.getElementById("root")!).render(
  <React.StrictMode>
    <App />
  </React.StrictMode>,
);

Where the image URL redirects to a location hosted by the Fastly CDN. We also have the following minimal HTML file:

<!doctype html>
<html lang="en">
  <head>
    <script type="module" crossorigin src="app.js"></script>
  </head>
  <body>
    <div id="root"></div>
  </body>
</html>

Disabling cache and throttling the network, the Firefox browser Waterfall looked like this:

Before Preloading Asset URL

After we added the following to the head:

<link rel="preload" href="https://picsum.photos/id/0/200" as="image" type="image/jpeg" />

The Waterfall then looked like this:

After Preloading Asset URL

Comparing these two Waterfalls, we see that the browser started loading the JPEG image much earlier after we added the preloading code, and resulted in an overall shorter loading time (from ~3.2 seconds to ~2.3 seconds).

Assets with Unknown URLs

Sometimes we know the app loads an asset from an external website but we don’t know the URL of the asset before runtime, such as an asset that is determined at runtime by the app’s logic. In this case, we can add the origin of the external website to a <link rel="preconnect" .../> block to the head section, or use the equivalent HTTP Link entity-header field. This informs the browser that the page requires connection to that external website and that the browser can start establishing a connection to that external website whenever it considers appropriate. As before, it is usually safer to add it before the script tag that loads the app. For example, if the app uses an asset from https://example.com but we don’t know the URL of the asset, we can add the following to the head section:

<link rel="preconnect" href="https://example.com" />

Experimental Comparison

Using the same app and code from the previous Experimental Comparison subsection, this time we pretended that we didn’t know the URL of the image and added the following to the head section of the HTML file:

<link rel="preconnect" href="https://picsum.photos" />

The Waterfall looked like this:

After Preconnect

We can see that, the loading time was reduced from a little over 3.2 seconds to a little lower than 3.2 seconds. The time saved mostly came from the saved connection time to picsum.photos (Note the zeroed 302 redirection time).

Defer Non-Essential Scripts

Chances are that your React app is loaded as a module, similar to the following:

<script type="module" crossorigin src="/assets/my-app.js"></script>

When a JavaScript file is loaded as a module, its loading is always deferred:

There is no need to use the defer attribute when loading a module script; modules are deferred automatically.

However, when a script is deferred, it is guaranteed to be executed only after non-deferred script. Additionally, deferred scripts are also executed in the order they appear in an HTML document. Therefore, we have the following principle to speed up loading: Set non-essential scripts, such as analytics scripts, to deferred, and move them after the script tag of the app. For example, if we also need to load a non-essential JavaScript file https://example.com/script.js, we add the following before the script tag of the app:

<script defer src="https://example.com/script.js"></script>

Preload iframe

If the React app is loaded via an iframe, we can make the browser aware of the iframe ahead of time by adding a <link rel="preload" as="document" ... /> block to the head section, or use the equivalent HTTP Link entity-header field, similar to the following:

<link rel="preload" href="https://example.com/app.html" as="document" />

Without this block, the browser will not load the iframe until it encounters the iframe when parsing the main HTML document. By hinting the browser upfront about the iframe, the browser can start loading the document ahead of time. The effectiveness largely depends on how much work the browser has between encountering the preload request and the iframe.

Conclusion