Chunk Preloading

Because reactolith resolves component tags lazily from the rendered HTML, the browser only discovers which chunks it needs after parsing the page. You can close that gap by emitting preload hints for the chunks a page actually uses — either as response headers (Link: rel=modulepreload, optionally flushed early as 103 Early Hints) or as <link rel="modulepreload"> tags in the document <head>.

HTTP/2 Server Push is not the mechanism here: it has been removed from Chrome and is effectively dead. The supported equivalents are Link preload hints and 103 Early Hints.

The general shape

Two things have to line up at request time:

  1. Which tags does this page use? The backend walks the rendered HTML once and collects every custom-element name (any local name containing a hyphen).
  2. Which JS chunk implements each tag? That mapping comes from your bundler's build manifest — the same artifact it already produces to fingerprint asset URLs.

With those two pieces, the backend emits one preload hint per used tag, either inline in the head:

<link rel="modulepreload" href="/assets/ui-button-7f3a9d.js"> <link rel="modulepreload" href="/assets/ui-input-2b1c0e.js">

or as a response header (which a CDN or reverse proxy can also turn into a 103 Early Hints response):

Link: </assets/ui-button-7f3a9d.js>; rel=modulepreload, </assets/ui-input-2b1c0e.js>; rel=modulepreload

Example: wiring it up with the Vite manifest

Build with the manifest enabled:

// vite.config.ts export default { build: { manifest: true, }, };

Vite writes dist/.vite/manifest.json. Each entry maps a source path to its hashed output file plus any CSS and shared imports that chunk pulls in:

{ "src/components/ui-button.tsx": { "file": "assets/ui-button-7f3a9d.js", "imports": ["_shared-9a2c4f.js"], "css": ["assets/ui-button-1c4e8b.css"] }, "src/components/ui-input.tsx": { "file": "assets/ui-input-2b1c0e.js" } }

The backend derives the source path from a tag name by the same convention the loader uses (e.g. ui-button → src/components/ui-button.tsx), looks it up in the manifest, and emits the hints — including the chunk's transitive imports and css so a single round-trip warms everything the component needs:

$manifest = json_decode(file_get_contents('dist/.vite/manifest.json'), true); // Tag names collected while rendering the page. $tags = ['ui-button', 'ui-input']; $links = []; foreach ($tags as $tag) { $entry = $manifest["src/components/{$tag}.tsx"] ?? null; if (!$entry) continue; $links[] = "</{$entry['file']}>; rel=modulepreload"; foreach ($entry['imports'] ?? [] as $importKey) { $imp = $manifest[$importKey] ?? null; if ($imp) $links[] = "</{$imp['file']}>; rel=modulepreload"; } foreach ($entry['css'] ?? [] as $css) { $links[] = "</{$css}>; rel=preload; as=style"; } } header('Link: ' . implode(', ', $links));

The same data can equally well be rendered as <link rel="modulepreload"> tags into the document head — pick whichever fits your stack. If your edge supports it, flush the Link header as a 103 Early Hints response so the browser starts fetching chunks while the backend is still rendering the body.

reactolith itself does not need any of this — it's a deployment-time optimization. It's documented here so backend bundles can settle on the same tag-scanning + manifest-lookup convention.

Eating the documentation

This documentation site runs the recipe above as a Vite build plugin — view source on any page and you'll find a block like this in the <head>, generated from the tags that page actually uses:

<!-- reactolith: preloaded component chunks --> <link rel="modulepreload" href="/assets/app-page-…​.js"> <link rel="modulepreload" href="/assets/code-block-…​.js"> <link rel="preload" as="style" href="/assets/code-block-…​.css">

The implementation lives in docs/vite-plugin-preload-component-chunks.ts — about 80 lines — and is wired into docs/vite.config.ts. After vite build writes dist/.vite/manifest.json, the plugin's closeBundle hook walks every built HTML, scans it for custom-element tags, resolves each one through a small tag→source-path function that mirrors the loader's prefix rules, and injects the resulting <link rel="modulepreload"> tags into the <head>. CSS sidecars and transitive shared chunks are followed too, so a single round-trip warms everything a component needs.