Chapter 33
Network Performance
Network performance for React SPAs — HTTP/3 awareness, React 19.2 resource hints (preload, preinit), image strategy, and service workers for offline-first.
Published 2026-05-23
🎯 Chapter Goal — After this chapter you can tune the network layer of a React SPA: HTTP/3 awareness, resource hints (
preload/preinitfrom React 19.2), image strategy that doesn’t cost 60 % of your page weight, and service workers for offline-first when the use case justifies it.🧭 Prerequisites — Ch 2 §2.5 (asset loading APIs), Ch 17 (Vite), Ch 30 (budget).
🔹 33.1 HTTP/3 and resource hints
HTTP/3 runs over QUIC (UDP) instead of TCP. Universal in 2026 across major CDNs (Cloudflare, Fastly, Akamai, AWS CloudFront). For most SPAs, you get HTTP/3 for free by deploying behind a modern CDN; no app code change.
What changes: head-of-line blocking is gone. Multiple requests over one connection no longer wait on each other. The practical implication: aggressive per-request splitting is cheaper than under HTTP/1.1.
Resource hints — preload, preinit, prefetchDNS, preconnect
React 19.2 ships these as JSX-callable functions (Ch 2 §2.5). The mental model:
prefetchDNS('https://api.acme.example') ← cheap; do early
preconnect('https://cdn.acme.example') ← DNS + TCP + TLS handshake
preload('/img/hero.webp', { as: 'image' }) ← fetch, don't apply
preinit('/css/route.css', { as: 'style' }) ← fetch and apply
Where to call them:
import { prefetchDNS, preconnect, preinit } from 'react-dom';
export const AppShell = ({ children }: { children: React.ReactNode }) => {
prefetchDNS('https://api.acme.example');
preconnect('https://cdn.acme.example', { crossOrigin: 'anonymous' });
preinit('/fonts/inter.woff2', { as: 'font', crossOrigin: 'anonymous' });
return <div className="shell">{children}</div>;
};
React-DOM dedupes. Calling prefetchDNS from every route is fine — one hint per host.
The waterfall difference is dramatic for first paint (see Ch 2 §2.5 mock).
🔹 33.2 Image strategy
Images often dominate page weight. Three layers:
Format
| Format | When to use |
|---|---|
| AVIF | Modern browsers; best compression |
| WebP | Wide support; fallback for old browsers |
| JPEG | Legacy fallback |
| PNG | Only when transparency + lossless required |
| SVG | Vector content (icons, logos, illustrations) |
Generate AVIF + WebP at build time; serve via <picture>:
<picture>
<source srcSet="/hero.avif" type="image/avif" />
<source srcSet="/hero.webp" type="image/webp" />
<img src="/hero.jpg" alt="…" width={1200} height={600} />
</picture>
Always include width / height (prevents CLS).
Responsive srcset
Serve smaller images to smaller viewports:
<img
src="/hero-800.jpg"
srcSet="/hero-400.jpg 400w, /hero-800.jpg 800w, /hero-1600.jpg 1600w"
sizes="(max-width: 600px) 400px, (max-width: 1200px) 800px, 1600px"
alt="…"
width={1600} height={800}
/>
The browser picks the best file for the device’s screen + DPR. A phone user doesn’t download the 1 600 px hero.
loading="lazy"
Native lazy-loading. Browser-supported everywhere in 2026:
<img src="/below-fold.jpg" loading="lazy" alt="…" />
Apply to anything below the fold. Save the initial-paint network for the above-fold images.
⚠️ Don’t lazy-load above-the-fold images. Browser delays the fetch; LCP suffers.
Image CDNs
For dynamic image needs (user-uploaded content, on-the-fly resizing), an image CDN (Cloudflare Images, imgix, Cloudinary) generates the right variant per request. Often cheaper than building a build-time pipeline.
🔹 33.3 Service workers and offline-first
Service workers cache responses and serve them from disk on subsequent visits — including offline.
When to add one:
- Users actually go offline (mobile, in-the-field tools, embedded devices).
- Predictable repeat-visitor patterns where caching app shells / API responses helps.
- You’re shipping a PWA installable to the home screen.
When NOT to add one:
- Public marketing site — users hit once, no benefit from SW caching, you pay debugging cost.
- Heavily-personalised app — SW caching the wrong user’s data is a security incident.
- Team without service-worker experience — SWs are subtle (cache lifecycle, message channels, scoping rules).
Workbox — the canonical library
https://web.dev/workbox/ — Google’s SW toolkit. Recipes for common patterns:
// sw.ts (generated via vite-plugin-pwa)
import { precacheAndRoute } from 'workbox-precaching';
import { registerRoute } from 'workbox-routing';
import { CacheFirst, NetworkFirst } from 'workbox-strategies';
precacheAndRoute(self.__WB_MANIFEST); // pre-cache the app shell
registerRoute(
({ url }) => url.origin === 'https://cdn.acme.example',
new CacheFirst(), // CDN assets: cache forever
);
registerRoute(
({ url }) => url.pathname.startsWith('/api/'),
new NetworkFirst({ networkTimeoutSeconds: 3 }), // API: prefer fresh, fall back to cache
);
vite-plugin-pwa wires the generation into Vite. Most teams should use it instead of hand-rolling a SW.
The update flow
When a new SW deploys, the old one keeps serving until the user closes every tab. Pattern: prompt the user to refresh on update.
// in your app
if ('serviceWorker' in navigator) {
const reg = await navigator.serviceWorker.register('/sw.js');
reg.addEventListener('updatefound', () => {
const sw = reg.installing!;
sw.addEventListener('statechange', () => {
if (sw.state === 'installed' && navigator.serviceWorker.controller) {
// new version installed; old still active
showUpdateBanner(() => sw.postMessage({ type: 'SKIP_WAITING' }));
}
});
});
}
The banner says “New version available — refresh to update.” User clicks; new SW activates; page reloads.
⚠️ A bad SW that you’ve shipped can be hard to remove. Practice the recovery path: ship a SW that unregisters itself, deploy, wait, then ship the real new SW.
🪤 Common Pitfalls
- Adding a service worker without a recovery plan — you can’t undo a bad one easily.
- Lazy-loading above-the-fold images → LCP regression.
- Missing
width/heighton<img>→ CLS. - JPEG when AVIF/WebP would have been 60 % smaller.
- Per-route
<link rel="stylesheet">blocking render → usepreinit. preloadfor every image on the page — defeats the purpose; only hint what you’ll need imminently.- Service worker that caches authenticated API responses → users see other users’ data.
✅ Recap
- HTTP/3 is free behind a modern CDN; design for many small chunks over one connection.
- Resource hints (Ch 2 §2.5) move the network waterfall left.
- AVIF/WebP + responsive
srcset+loading="lazy"is the 2026 image baseline. - Service workers are powerful but subtle — adopt deliberately, with a recovery plan.
🔗 Further Reading
- https://web.dev/articles/preload-critical-assets — web.dev preload guidance.
- https://developer.mozilla.org/en-US/docs/Web/Media/Formats/Image_types — MDN image-formats reference.
- https://web.dev/workbox/ — Workbox 7.x for service-worker patterns.
- https://vite-pwa-org.netlify.app/ —
vite-plugin-pwa(Workbox + Vite integration). - Ch 2 §2.5 (asset hints), Ch 30 (budgets).
In the book — not on the site
Each topic has an 🧠 Under-the-hood subsection — the algorithm, the data structures, what React DevTools surfaces, debugging recipes. Plus a 🧪 hands-on lab per chapter with a starter repo. Reserved for the book.