The Duel
One app. The same .razor sources. Two compilers.
A routed task board (EditForm add, keyed list with per-row toggle/remove, filters, LINQ stats, a second page), compiled once byBlazor WebAssembly and once byFilament, from thesame page files. Every number below was taken only after a10-step behavioural contract (add, toggle a persisting row, all three filters, per-row remove, navigate, remount afresh, Back) passed identically on both sides.
Weight on the wire
403× less to download
Cold cache, service workers blocked, every byte counted by CDP from navigation until the app is interactive and the network idle. Brotli negotiated, as production would.
Time to interactive
6.7× sooner (over localhost)
Navigation to the app answering its ready probe, the instant observed in-page by a MutationObserver, never a frame boundary. Median of 10 cold loads. Localhost flatters the heavier side: on a real network, the megabytes above arrive first.
Memory
50× smaller footprint
performance.measureUserAgentSpecificMemory() on a cross-origin-isolated page: the one instrument that attributesWebAssembly linear memory to the page, so Blazor's .NET heap is counted, not hidden. Measured after the full interaction burst; median of 5 cold pages; per-run attribution breakdowns kept in the committed JSON.
All the numbers
| Measure | Blazor (no JIT) | Blazor (AOT) | Filament |
|---|---|---|---|
| Wire bytes to interactive (gzip) | 2.13 MB | 5.87 MB | 5.0 KB |
| Wire bytes to interactive (brotli) | 1.75 MB | 4.02 MB | 4.4 KB |
| Time to interactive (brotli, median) | 180 ms | 233 ms | 26.9 ms |
| Time to interactive (IQR) | 3.59 ms | 4.79 ms | 0.97 ms |
| Memory after load | 41.47 MB | 45.16 MB | 752.7 KB |
| Memory after interaction burst | 41.65 MB | 45.33 MB | 858.7 KB |
| Requests to interactive | 48 | 48 | 3 |
How this was measured
- Same source, both sides. The Blazor app compiles
baseline/Duel.Blazor/Pages/*.razor; Filament compilesthe very same files via its--routermode. Nothing is ported. - Contract first, numbers second. The harness refuses to report any number for an app that fails the shared 10-step DOM contract. An app doing less work can never read as fast.
- Mirrored passes. Three labels × two encodings, order reversed between halves (
bench/run-duel.sh), so thermal or scheduler drift cannot masquerade as a framework effect. Strictly sequential, cold cache each run. - Environment, recorded. Chrome 150.0.7871.129, .NET SDK 10.0.301, harness v1.54.0, all pinned inside every committed JSON inbench/results/duel/.
- The full log. Entry n°60 ofBENCH.mdrecords this run, its reserves, and every command to reproduce it.