Simulating Slow Networks for Wasm Loading
This page answers one task: on a fast development machine and network, the WebAssembly feature loads instantly, but users on phones and mobile networks report waiting, and you want to reproduce their experience locally and measure it, so loading strategies can be compared before they ship.
Prerequisites
- [ ] A production build (compressed, minified) served locally or from staging.
- [ ] Chrome DevTools, and optionally Playwright or WebPageTest for scripted runs.
- [ ] A rough idea of your users’ networks and devices from analytics or field data.
What makes Wasm loading slow for real users
WebAssembly loading has three costs: transferring the bytes, compiling them, and instantiating plus running startup code. A developer machine hides all three — local servers deliver megabytes in milliseconds, and fast CPUs compile them quickly. On a mid-range phone over a congested 4G connection, a 2 MB compressed module can take two seconds to download and several hundred milliseconds to compile. Streaming compilation overlaps the two, so the total is not simply the sum, but only when headers and loading code are right.
Simulation reproduces both constraints. Network throttling limits bandwidth and adds latency; CPU throttling slows script execution and compilation. Neither is perfect — DevTools throttling shapes requests at the browser level rather than the radio, and CPU throttling scales JavaScript work more faithfully than it scales Wasm compilation threads — but both make problems visible that are otherwise invisible during development.
Step 1 — throttle network and CPU in DevTools
In the Network panel, choose a preset (“Slow 4G”, “Fast 4G”) or a custom profile — for example 1.6 Mbps down, 150 ms latency — and in the Performance panel
set CPU throttling to 4× or 6×. Disable the cache for cold-load tests. Then record a Performance trace while loading the feature. The trace shows the
.wasm request, compile tasks (v8.wasm.compileStreaming or similar), instantiation and the first call, each with its duration on the throttled profile.
Step 2 — compare cold and warm loads
Users who return find the module in the HTTP cache and, often, compiled code in the browser’s code cache. Test three states: cold (cache disabled), warm
HTTP cache (cache enabled, second load), and warm code cache (third load, where Chrome reuses compiled code for unchanged responses). The difference between
cold and warm loads shows how much caching strategy matters; if warm loads are not much faster, check Cache-Control headers and whether the URL changes
between loads, as described in
setting cache-control headers for Wasm.
Step 3 — script repeatable runs
Manual throttling is good for exploration; scripts make comparisons reliable. With Playwright and the Chrome DevTools Protocol:
import { chromium } from "playwright";
const browser = await chromium.launch();
const page = await browser.newPage();
const cdp = await page.context().newCDPSession(page);
await cdp.send("Network.enable");
await cdp.send("Network.emulateNetworkConditions", {
offline: false, latency: 150, downloadThroughput: 1.6e6 / 8, uploadThroughput: 750e3 / 8,
});
await cdp.send("Emulation.setCPUThrottlingRate", { rate: 4 });
await page.goto("http://localhost:4173/editor", { waitUntil: "load" });
const t = await page.evaluate(() => performance.getEntriesByName("wasm-ready")[0]?.startTime);
console.log(`wasm ready at ${t?.toFixed(0)} ms`);
Have the page set a performance.mark("wasm-ready") when the module is usable, so scripts measure the moment users care about. Run each configuration
several times and report the median; throttled runs still vary.
Step 4 — try loading strategies under throttling
With a repeatable setup, compare strategies on the same profile: eager versus lazy loading, preloading on hover or idle, compression levels, splitting the module, compiling in a worker. The throttled numbers make trade-offs concrete — for example, that preloading on hover saves 800 ms of perceived wait on slow 4G but nothing on fast connections. Strategies are covered in lazy loading Wasm on first use and preloading Wasm with link rel=preload.
Step 5 — validate against real devices and field data
Simulation approximates; real devices decide. Periodically run the same scripted scenario on a real mid-range Android phone over a real mobile connection (remote debugging or a device lab), and compare with field data from real-user monitoring. If field data is consistently worse than the simulation, adjust the profile — slower bandwidth, higher latency, stronger CPU throttling — until the simulation predicts reality, then use it to evaluate changes before they ship.
Testing in CI with performance budgets
A throttled loading test in CI turns these measurements into a guard. Run the scripted scenario against the production build on every change, record time
to wasm-ready on the slow profile, and fail when it exceeds a budget or regresses beyond a tolerance from the main branch’s baseline. Throttled timings in
CI vary with machine load, so use medians of several runs and generous tolerances, and run the job on dedicated or consistent runners. Even a coarse guard
catches the large regressions — a module that doubled in size, compression that stopped working, a lazy import that became eager — that matter most to
users on slow networks.
Lighthouse and WebPageTest
Lighthouse’s mobile profile applies simulated throttling and reports Largest Contentful Paint, Total Blocking Time and Time to Interactive, which capture how a Wasm module on the critical path affects page load. It does not measure when your feature becomes usable unless you add a mark and read it from the trace. WebPageTest runs real browsers on real devices and network shaping at the packet level, closer to reality than DevTools throttling, and its filmstrip view shows what users see while the module loads. Use Lighthouse in CI for page-level budgets and WebPageTest for periodic deep dives.
Simulating flaky and interrupted connections
Slow is not the only network problem users have. Mobile connections drop, stall and recover; captive portals return HTML instead of the requested file;
corporate proxies time out long downloads. A WebAssembly feature that downloads megabytes is exposed to all of these. Test them deliberately: use the
DevTools “Offline” toggle mid-download, Playwright’s request interception to abort the .wasm request after a delay or return a truncated body, and a local
proxy such as toxiproxy to add jitter and packet loss. Check that the loader reports a clear error, offers a retry, and does not leave the feature in a
half-initialised state where every later call fails. A service worker that caches the module after the first successful load makes later visits resilient,
and the same interception tools can confirm that the cached path works offline.
Communicating loading to users
The point of simulating slow networks is to design for them. Once you can see a two-second wait, decide what the user sees during it: a progress
indicator driven by download progress (readable from a streamed fetch with known Content-Length), a usable interface with the Wasm-dependent controls
disabled and labelled, or a lighter JavaScript fallback that works until the module arrives. Each choice can be tested on the throttled profile,
including how it feels when the download fails halfway.
Expected output
On the slow 4G profile with 4× CPU throttling, the editor’s module is ready at 2.7 s on a cold load, 0.6 s with a warm HTTP cache and 0.3 s with a warm code cache; preloading on hover reduces perceived cold-load wait to 0.9 s; and a CI job fails if the cold-load figure exceeds 3.2 s.
Gotchas
- Testing the development build. Uncompressed, unminified output distorts timings. Test the production build.
- Forgetting cache state. Cold and warm loads differ by seconds. Test both explicitly.
- Single runs. Throttled timings vary. Report medians.
- Trusting simulation alone. Calibrate against real devices and field data.
- Measuring page load instead of feature readiness. Mark when the module is usable.
Performance note
Moving the 2 MB module from an eager import to lazy loading with preload-on-hover cut Largest Contentful Paint on the slow 4G profile from 3.4 s to 1.9 s, while the feature itself became ready 0.4 s later than before when users clicked immediately — a trade-off visible only under throttling.
Frequently Asked Questions
Does CPU throttling slow Wasm compilation realistically? Approximately. Compilation runs on background threads that throttling affects less; validate on real devices.
Which network profile should I use? Start from your field data’s 75th-percentile connection; “Slow 4G” is a reasonable default for mobile audiences.
Can I throttle in Firefox? Firefox DevTools offers network throttling; CPU throttling is Chromium-only.
Should CI fail on throttled timings? Yes, with medians and tolerances, for large regressions; small changes are noise.
How do I test a download that fails halfway?
Abort or truncate the .wasm response with Playwright’s request interception, and check the loader’s error and retry behaviour.
Related
- Measuring Wasm startup time end to end — breaking down the phases.
- Serving precompressed Wasm locally — production-like delivery.
- Reducing Wasm cold-start latency — what to fix.
- Measuring Wasm performance with real-user monitoring — field data for calibration.
← Back to Local Development Server Configurations