Advanced Topics
Speculation Rules, bfcache, streaming SSR, memory leaks, and build-time performance engineering.
These topics separate senior engineers from “I ran Lighthouse once” practitioners.
Speculation Rules API
Declaratively prerender or prefetch likely next navigations:
<script type="speculationrules">
{
"prerender": [{
"where": { "href_matches": "/product/*" },
"eagerness": "moderate"
}],
"prefetch": [{
"where": { "href_matches": "/docs/*" },
"eagerness": "conservative"
}]
}
</script>
| Mode | Effect |
|---|---|
prefetch | Fetch resources for next page |
prerender | Full render in hidden tab — instant navigation |
Caution: prerender costs bandwidth/CPU — use eagerness and match rules tightly.
Back/forward cache (bfcache)
Instant back navigation when page is frozen in memory.
Blocks eligibility:
unload/beforeunloadlisteners- Open
IndexedDBtransactions without closure Cache-Control: no-storeon main document- Certain APIs keeping live connections
Test: DevTools → Application → Back/forward cache → “Test bfcache”.
Priority hints beyond LCP
<script src="/critical.js" fetchpriority="high"></script>
<script src="/analytics.js" fetchpriority="low" defer></script>
fetchpriority on <link rel=preload> coordinates with image/script contention.
Streaming SSR
Send HTML shell immediately, stream slow sections:
// Conceptual — framework-specific APIs vary
res.write('<html><head>…</head><body>');
res.write(fastHeader);
await streamSlowSidebar(res);
res.write('</body></html>');
Improves FCP and perceived load; LCP may still wait on slow fragment — prioritize LCP chunk in first stream.
Islands & partial hydration mental model
| Approach | JS shipped |
|---|---|
| SPA full hydration | Entire app tree |
| SSR + hydrate all | HTML + full client bundle |
| Islands (Astro, etc.) | Per-component |
This academy site uses Astro: lessons are static HTML; labs ship scoped <script> modules only on pages that need interactivity.
Memory & leaks
Symptoms: tab slows over time, mobile kills tab, DevTools heap grows unbounded.
Find:
- Memory → Heap snapshot → compare snapshots
- Detached DOM trees (listeners holding nodes)
- Global caches without eviction
- Forgotten
setInterval/ observers
const observer = new MutationObserver(callback);
// Later:
observer.disconnect();
Build-time performance
| Technique | Benefit |
|---|---|
| Code splitting | Smaller initial JS |
| Tree shaking | Dead code elimination |
| Minification + compression (brotli) | Transfer size |
modulepreload for entry graph | Faster parse/execute |
| CDN immutable caching | Repeat visit speed |
| Image pipeline (build plugin) | AVIF/WebP at build |
HTTP/3 & connection coalescing
QUIC reduces head-of-line blocking. Still minimize origins — each origin has connection cost even with HTTP/3.
Expert habits
- Field first on user-facing routes
- Attribute before optimizing (which script, which element)
- One metric per PR when possible — isolate regressions
- Document budgets in repo (
perf-budgets.json) - Teach the team — perf is a system property, not a hero task
You’ve completed the curriculum. Use Labs for hands-on practice, Cheatsheet for quick reference, and Resources for deep external reading.