Skip to content

Performance · Concept

Resource hints (preload and preconnect)

Published

Resource hints are <link> relations in <head> that start work before the parser would discover the need for it: preload fetches one resource the current page will use, modulepreload does the same for an ES module and its dependencies, preconnect opens a connection to an origin without fetching, dns-prefetch resolves only the name, and prefetch fetches a resource for a later navigation at low priority. In a PWA they shorten first visits, where the service worker has not yet cached anything.

The preload scanner finds <link> hints early in the HTML and queues the work at the priority implied by the as type: as="style" is fetched at the highest priority, as="font" and as="script" high, as="image" at the image’s usual priority unless fetchpriority says otherwise. A preloaded response waits in the memory cache for the real request, which must match the preload’s URL, destination, and CORS mode; a mismatch means two fetches (Preload critical assets, web.dev).

Attributes that decide whether the preload is used

Section titled “Attributes that decide whether the preload is used”
  • as. Required. Without it the fetch is treated like an XMLHttpRequest with the wrong priority and the wrong Accept header, and it does not match the later request.
  • crossorigin. Fonts are fetched in anonymous CORS mode from any origin, so a font preload needs crossorigin even for a same-origin file; a <script crossorigin> or a fetch() likewise needs a matching crossorigin on the preload.
  • type. Lets the browser skip the preload for a format it does not support, which is how a WOFF2 preload stays harmless in an engine that would use the WOFF fallback.

preconnect pays for itself when a cross-origin request is certain and soon (a font host, the API origin); each connection costs a socket, so more than a handful is counterproductive, and dns-prefetch is the cheaper hint for the rest. modulepreload is the preload for import() chunks the page will need at startup, and it also links the module so evaluation starts sooner. prefetch is for the next navigation, not this page, and is superseded for documents by speculation rules.

preload shipped in Chrome 50 and Safari 11.1; Firefox enabled it in 56, disabled it again in 57 over web compatibility problems, and shipped it for good in 85 (BCD html.elements.link.rel.preload, bug 1405761). preconnect is in Chrome 46, Firefox 39, Safari 11.1; modulepreload in Chrome 66, Firefox 115, Safari 17; prefetch is in Chromium and Firefox and behind a flag in Safari. An unsupported hint is ignored, so the page loads the same way minus the head start.

The markup example is the common case; the script examples cover hints added at runtime.

Preloading the web font the first paint needs

Section titled “Preloading the web font the first paint needs”

The font is discovered only after the stylesheet is parsed; the hint starts it with the HTML. crossorigin is required or Chrome fetches it a second time for the @font-face request.

<link rel="preload" href="/fonts/brand-sans.woff2" as="font" type="font/woff2" crossorigin>
<link rel="preconnect" href="https://api.example.com">
<link rel="modulepreload" href="/scripts/app.3f2a1c.js">

The preconnect warms the API origin the shell will call after it renders; the modulepreload starts the main chunk and its imports before the module script tag is reached.

A hint inserted at runtime, for instance when a route becomes likely, behaves like one in the HTML. Set as and crossOrigin before href so the fetch starts with the right parameters.

function preloadFont(href) {
const link = document.createElement('link');
link.rel = 'preload';
link.as = 'font';
link.type = 'font/woff2';
link.crossOrigin = 'anonymous';
link.href = href;
document.head.append(link);
}

A preload that the page does not use triggers the console warning described below, so insert hints only for resources the current page will request.

link.relList.supports() reports whether the engine knows a relation, which is the test to run before relying on modulepreload or prefetch; the fallback is to do nothing, because the real request still happens later.

function supportsRel(rel) {
const link = document.createElement('link');
return 'relList' in link && typeof link.relList.supports === 'function' && link.relList.supports(rel);
}
if (supportsRel('modulepreload')) {
preloadModule('/scripts/editor.3f2a1c.js');
} else {
// unsupported: the import() will fetch the chunk when it runs
}

supports() answers from the engine’s relation list, not from a network test, so it is cheap enough to call per hint.

Specifications

SpecificationStatus
None.