Navigation Performance
Cross-app navigation means full page loads. Speculation Rules make those navigations feel instant by prefetching before users click.
Outcome
Implement Speculation Rules in the shared Header to prefetch and prerender cross-app pages before users click.
The Problem: Hard Navigations
In a single-page app, clicking a link triggers a client-side route change. It's fast and seamless. In microfrontends:
Click "Docs" in marketing app
└── Full page load to docs app
└── HTML request
└── JavaScript download
└── React hydration
└── Visible content
This takes longer than a client-side transition. Users notice.
The Solution: Speculation Rules
Speculation Rules tell the browser to prefetch or prerender pages before users navigate:
- Prefetch - Download the HTML and critical resources
- Prerender - Fully render the page in a hidden tab
When the user clicks, the page is already ready.
Speculation Rules only work in Chrome 109+ and Edge 109+. Safari and Firefox ignore the script. No error, just no prefetch optimization for ~25% of users.
Fast Track
- Add Speculation Rules script to the shared Header
- Configure prefetch for moderate eagerness
- Configure prerender for conservative eagerness
Hands-on Exercise 3.1
Add Speculation Rules to the shared Header component.
Part 1: Create the Speculation Rules Component
Create packages/ui/src/speculation-rules.tsx:
export function SpeculationRules() {
const rules = {
prefetch: [
{
source: "document",
where: {
and: [
{ href_matches: "/*" },
{ not: { href_matches: "/api/*" } },
{ not: { href_matches: "/_next/*" } },
],
},
eagerness: "moderate",
},
],
prerender: [
{
source: "document",
where: {
and: [
{ href_matches: "/*" },
{ not: { href_matches: "/api/*" } },
{ not: { href_matches: "/_next/*" } },
],
},
eagerness: "conservative",
},
],
};
return (
<script
type="speculationrules"
dangerouslySetInnerHTML={{ __html: JSON.stringify(rules) }}
/>
);
}moderate eagerness prefetches on hover. conservative only prerenders when the click starts (mousedown).
Part 2: Add to Header
Update packages/ui/src/header.tsx:
import { SpeculationRules } from "./speculation-rules";
export function Header() {
return (
<>
<SpeculationRules />
<header className="h-16 border-b bg-white px-6 flex items-center justify-between">
{/* ... existing header content ... */}
</header>
</>
);
}Part 3: Export the Component
Update packages/ui/src/index.ts:
export { Header } from "./header";
export { Footer } from "./footer";
export { SpeculationRules } from "./speculation-rules";Understanding Eagerness Levels
| Level | When It Triggers | Use Case |
|---|---|---|
immediate | Page load | Critical paths users always visit |
eager | Link appears in viewport | High-traffic links |
moderate | Hover or focus on link | Most navigation links |
conservative | Click starts (mousedown) | Resource-heavy pages |
For cross-app navigation:
- Prefetch: moderate - Download resources when user shows intent
- Prerender: conservative - Only fully render when click is certain
This balances performance gains against resource usage.
Try It
- Run
pnpm dev - Open DevTools → Network
- Visit
localhost:3024(Marketing) - Hover over "Docs" in the header
- Watch the network tab - you should see prefetch requests for
/docs - Click "Docs" - the navigation should feel faster
Measuring the Improvement
Without Speculation Rules:
Click → DNS → Connection → HTML → JS → Hydration → Interactive
~50ms ~50ms ~100ms ~200ms ~150ms
Total: ~550ms
With prefetch:
Hover → Prefetch (background)
Click → HTML (cached) → JS → Hydration → Interactive
~0ms ~200ms ~150ms
Total: ~350ms (36% faster)
With prerender:
Click-start → Prerender (background)
Click-end → Swap (instant)
Total: ~50ms (90% faster)
Commit
git add -A
git commit -m "perf: add Speculation Rules for cross-app prefetching"Done-When
- SpeculationRules component created
- Header includes Speculation Rules script
- Prefetch triggers on hover (moderate)
- Network tab shows prefetch requests on hover
Advanced: Targeted Speculation
For high-traffic pages, increase eagerness:
const rules = {
prefetch: [
// High-traffic: prefetch eagerly
{
source: "document",
where: { href_matches: ["/docs", "/pricing"] },
eagerness: "eager",
},
// Everything else: prefetch on hover
{
source: "document",
where: { href_matches: "/*" },
eagerness: "moderate",
},
],
};What's Next
Write tests that catch routing misconfigurations before they reach production.
Was this helpful?