Modulepreload: fetch a JavaScript module before it is imported

Use <link rel="modulepreload" href="./details.js"> when you want the browser to prepare a module before code imports it. The link fetches the module and prepares it for the module map; it does not execute the module. Execution still follows a module script or an import.

This distinction helps with a details panel whose module is imported only after a reader requests it. You can prepare that file while leaving its behavior inactive. Whether the earlier request improves the page depends on the network and the other resources competing with it.

Keep preparation separate from execution

Create these two files in a folder served over HTTP. The complete index.html is:

<!doctype html>
<html lang="en">
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>Workshop details</title>
<link rel="modulepreload" href="./details.js">
<button id="show" type="button">Show room details</button>
<p id="result">Details have not been opened.</p>
<script type="module">
  document.querySelector('#show').addEventListener('click', async () => {
    try {
      const { roomDetails } = await import('./details.js');
      document.querySelector('#result').textContent = roomDetails;
    } catch {
      document.querySelector('#result').textContent = 'Details could not load.';
    }
  });
</script>
</html>

Save this as details.js:

globalThis.detailsExecutions = (globalThis.detailsExecutions ?? 0) + 1;
export const roomDetails = 'Room B: enter through the courtyard door.';

Before the button is pressed, the expected paragraph remains Details have not been opened. The modulepreload request can have completed while globalThis.detailsExecutions is still undefined. After the import, the paragraph should show the Room B instruction and the counter should be 1. Importing the same resolved module URL again uses its existing module instance within that document rather than repeating its top-level evaluation.

The fictional room instruction is a visible execution result. The counter exposes the boundary between a fetched module and code that has actually run. MDN modulepreload reference

Match the module URL and request conditions

The link and the later import above refer to the same resolved details.js URL. A query string, different folder, or changed base URL can make them refer to different resources. Inspect the requested addresses before assuming a second request is unnecessary.

Modulepreload uses CORS fetching and has module-specific behavior. A generic rel="preload" as="script" link is not an interchangeable way to populate the module map. Cross-origin modules require server permission. Keep credentials settings consistent with the eventual module request; do not add use-credentials unless the application actually needs that request mode.

Browsers may also fetch dependencies of the preloaded module, but do not assume an identical dependency-discovery strategy in every browser. If a known dependency matters, inspect its actual request and decide whether it needs an explicit modulepreload link.

For resolving bare names such as room-details, use an import map. An import map chooses a URL; a modulepreload link prepares a module at a URL. The two decisions address different parts of loading.

Confirm behavior before measuring speed

Open the served page with DevTools Network visible. Confirm that details.js loads before the first click. Inspect the counter in the console, then click the button and confirm both the instruction and counter. Press it again: the counter should remain 1.

Next compare a run without the modulepreload link under the same cache and network conditions. That run should start the details-module request when the button triggers the import. Record request timing and interaction behavior rather than claiming a fixed speed improvement from the tag alone.

Preloading every possible panel can spend bandwidth on code the reader never requests. Keep the link for a module whose early preparation suits the actual page. Retain the error message so a failed import leaves an understandable state. The module script guide covers the underlying HTTP server, import/export names, and file paths if the module fails to load.

Keep reading