Code splitting trades bytes for latency: the chunk is not downloaded until the route is visited, so the first click pays for the round trip. Preloading buys that back by starting the download while the user is still reading the current page. React 7,897 DOM exposes four hint functions any component may call during render, and deduplicates them, so ten components asking for one URL still emit one tag.
import { preload, preinit, preconnect, prefetchDNS } from 'react-dom';
export default function Page() {
prefetchDNS('https://cdn.example.com');
preconnect('https://api.example.com');
preload('https://example.com/hero.avif', { as: 'image', fetchPriority: 'high' });
preinit('https://example.com/theme.css', { as: 'style' });
console.log([...document.head.children].filter((el) => el.tagName === 'LINK')
.map((el) => el.outerHTML).join('\n'));
return <p>...</p>;
}<link rel="stylesheet" href="https://example.com/theme.css" data-precedence="default"> <link rel="dns-prefetch" href="https://cdn.example.com"> <link rel="preconnect" href="https://api.example.com"> <link rel="preload" href="https://example.com/hero.avif" as="image" fetchpriority="high">
The four differ in how far they go. prefetchDNS resolves a hostname, preconnect also completes the TCP and TLS handshake, preload downloads bytes into the cache without running them, and preinit downloads and applies them — which is why the stylesheet came back as an active rel="stylesheet" while the image stayed a rel="preload". preloadModule and preinitModule do the same for ES modules, which is what a chunk is.
For route chunks the easiest win is the router's own hint: in React Router 139,386 's framework mode <Link prefetch="intent"> fetches a route's module and data when the pointer enters the link, and prefetch="viewport" when it scrolls into view. Without a framework, onMouseEnter calling your lazy component's own import() does the same. Hint only the one or two paths people take next: a preload nobody needs competes for bandwidth with one they do.