Key Principle
Every resource optimization exists on a spectrum between "Too little Too late" and "Too much Too soon." Loading resources too eagerly steals bandwidth from critical-path assets; loading them too lazily delays interactivity. The goal is to sequence resources so each Core Web Vital milestone (FCP, LCP, FID) fires as early as possible without starving the next one.
Why This Matters
- Resources must follow the metric dependency chain: FCP requires critical CSS and fonts; LCP requires the hero image ready by FCP; FID requires JS downloaded, parsed, and executed by LCP.
- Sequential downloads can outperform concurrent ones when they allow CPU work to overlap with network fetches, eliminating "Dead Time" where both CPU and network sit idle.
- Third-party scripts carry their own JS, images, and fonts and typically lack incentive to optimize for the host site's loading performance.
- HTTP/2 prioritization is inconsistent across CDNs and servers, making predictable resource ordering difficult without explicit hints.
Good Examples
PRPL Pattern
Push critical resources, Render initial route, Pre-cache remaining assets via service worker, Lazy-load non-critical routes. Each step has a causal purpose: Push minimizes roundtrips; Render prioritizes perceived speed; Pre-cache converts push-only (ephemeral) into push-then-cache (persistent); Lazy-load keeps non-critical routes off the critical path entirely.
Ideal 1P Loading Sequence
- Parse HTML + small inline scripts (avoid network round-trips for trivial setup).
- Inline critical CSS (eliminates the extra fetch that blocks first paint).
- Fetch critical fonts + LCP image; preconnect for cross-origin. FCP fires.
- Non-critical async CSS, 1P JS, above-the-fold images. 1P JS fetches before ATF images because JS has a longer pipeline (download + parse + execute). LCP fires.
- Below-the-fold images (no CWV impact). Visually Complete.
- Non-critical CSS parsed (must finish before FID to prevent layout shifts).
- Execute 1P JS, hydrate, lazy-load remaining chunks. FID fires.
Tree Shaking
Bundlers model source files as a directed graph; traversal from the entry point marks reachable nodes and prunes the rest. Only works with ES module syntax -- CommonJS require defeats static analysis. Side-effect-producing modules cannot be eliminated even when no export is referenced; marking packages side-effect-free in package.json unlocks deeper pruning.
List Virtualization
Renders only visible items (O(visible) DOM nodes instead of O(n)). Benchmark with 10K items: render time drops from 242.7 ms to 2.4 ms (~100x), frame rate rises from 31.5 fps to ~60 fps, GPU memory drops from 20.6 MB to 4.8 MB.
HTTP/2 Multiplexing
Replaces HTTP/1.1's 6-connection sequential model with binary frames over a single TCP connection, enabling concurrent request/response interleaving. This makes HTTP/1.1-era bundling strategies (concatenating files to reduce request count) less necessary and enables finer-grained caching of individual modules.
Counterpoints
- Preload is frequently overused. It forces unconditional parallel download regardless of network conditions and can delay FCP by shifting bandwidth away from hero images and fonts. Prefer
<script defer>in<head>orpreconnectfor cross-origin resources over explicit preload. - Server push lacks cache awareness. Pushed resources do not persist across visits, so returning users receive redundant data unless service workers close the gap.
- Excessive code-splitting granularity degrades compression ratios and browser performance. Manual boundary identification via dynamic imports is error-prone.
- Prefetched bytes are speculative. On metered connections, prefetching wastes bandwidth if the resource is never used. Only prefetch resources with high probability of use.
- Unbundled modules under HTTP/2 offer finer cache granularity but depend on correct server-side prioritization, which many CDNs implement partially or not at all.
Key Quotes
- "The trick is to find a balance between 'Too little Too late' and 'Too much Too soon'." (Ch: Resource Loading Optimization)
- "Libraries and npm packages are often not published in ES module format. This makes it hard for bundlers to tree shake and optimize." (Ch: Resource Loading Optimization)
- "We recommend avoiding preload as much as possible because it forces manual preload on every preceding resource and also causes manual curation of ordering. Preload should be especially avoided on fonts, as it is tricky to detect critical fonts." (Ch: Ideal Resource Loading Sequence, p. 4)
- "Having to repeatedly request the resources isn't optimal, as we're trying to minimize the amount of round trips between the client and the server!" (Ch: PRPL Pattern, p. 5)
- "When used incorrectly, preloading can cause your image to delay First Contentful Paint (e.g CSS, Fonts) -- the opposite of what you want." (Ch: Preload)
- "server push is not HTTP cache aware" (Ch: PRPL Pattern -- HTTP/2 and Server Push, p. 5)
- "Although we're not referencing the exports of the module itself, if the module has exported values to begin with, the module cannot be tree-shaken due to the special behavior when it's being imported!" (Ch: Tree Shaking, Side Effects, p. 5)
Rules of Thumb
- Sequence resources to mirror the CWV dependency chain: critical CSS before fonts, fonts before hero image, hero image before 1P JS, 1P JS before 3P scripts.
- Inline critical CSS; proxy cross-origin CSS to avoid extra connection overhead.
- Inline font-CSS declarations to collapse the HTML-to-CSS-to-font discovery chain; use preconnect (not preload) for the font files themselves.
- Lazy-load below-the-fold images to prevent bandwidth contention with 1P JS.
- Use ES module syntax everywhere to enable tree shaking; mark packages side-effect-free in
package.json. - Preload sparingly and measure impact. Prefer preconnect for cross-origin,
<script defer>for first-party JS discovery. - Prefetch only high-probability resources during idle time; avoid on metered connections.
- Virtualize long lists -- rendering only visible items yields ~100x render-time improvement at scale.
- Under HTTP/2, prefer granular modules over mega-bundles for better cache invalidation, but verify your CDN implements correct prioritization.
- Audit third-party scripts separately -- they compete for bandwidth and main-thread time without incentive to optimize for your metrics.
Related References
- Code Splitting and Dynamic Imports (route-level lazy loading)
- Critical Rendering Path (CSS/font blocking behavior)
- Service Worker Caching Strategies (Cache API, offline capability)
- Progressive Web App App Shell Architecture
- Core Web Vitals (FCP, LCP, FID/INP, CLS measurement)