Four clients, one spec, one frozen suite, one measurement pass. Every number below comes from a file in the public evidence, five runs per metric, medians with the range, on one PC in November 2026. The short version: React and Angular are within a few percent of a no-framework baseline on every startup and interaction number; Blazor WebAssembly ships 28 times React's bytes and pays for it on a slow link and on a phone; Blazor Server loads like a static site and then charges a round trip for every click. The AI built all four without a hand fix. The seven hypotheses written before the first build are settled one by one at the end, and three of them were wrong.

Steps in this part
  1. Read Part 1 first if you have not: it says what was built, by whom, and under which rules
  2. Read each table with its baseline row: the reference client is the same app with no framework at all
  3. Where a Blazor Server 100 ms row looks mild, read the WebSocket note under the interactions table before trusting it
  4. Check the seven hypotheses against your own expectations before you read the verdicts
  5. Clone the repository; every raw JSON file the tables quote is listed in its results page
  6. Rerun one metric yourself with the commands at the end and compare the medians

A. Size: on disk and on the wire

Production build on disk (before precompression), Brotli bytes of the files over 1 KB, and a cold load of /lists/1: requests and bytes on the wire, headers and API responses included
ClientFilesBytes on diskBrotli bytesCold requestsCold bytesWarm reload bytes
Reference (no framework)553,41312,750719,2614,597
React 195308,48981,234787,7804,619
Angular 226335,96086,0479108,0785,597
Blazor WebAssembly10018,416,9254,079,883532,461,7745,114
Blazor WebAssembly, AOT10030,933,2936,335,002534,716,8965,119
Blazor Server70 (whole publish folder)9,889,2811,259,494 (SDK, 60 files)2078,18115,539
Bar chart of bytes on the wire for a cold load: reference 18.8 KB, React 85.7 KB, Angular 106 KB, Blazor WebAssembly 2,404 KB, Blazor WebAssembly AOT 4,606 KB, Blazor Server 76.3 KB
Cold load of the first list, compressed, as Chrome's network layer counted it. Blazor Server's 124 KB of WebSocket frames per load are not in its bar.

A cold load of the first list costs React 88 KB and Angular 108 KB. Blazor WebAssembly costs 2.46 MB in 53 requests, and the AOT build 4.72 MB: the runtime, the framework assemblies and the app, Brotli-compressed by the same tool at the same level as every other client. Blazor Server's 78 KB is the prerendered page and the framework script; the rows arrive over the WebSocket, about 124 KB of frames per load, which the request counter does not see. Warm reloads are small everywhere because every client's files answer 304, except Blazor Server, which renders the page again on the server.

B. Startup

Time to the first visible task row (median of 5, min and max) and Lighthouse 13 on /lists/1 (median score of 5 runs, with LCP)
ClientFirst row, unthrottledFirst row at 100 ms RTTDesktop scoreDesktop LCPMobile scoreMobile LCPMobile TBT
Reference211.4 ms (119.3, 215.4)564.7 ms (533, 595.8)100331.97 ms99929.8 ms0
React 19208 ms (126.3, 232.2)553.6 ms (490.6, 675.6)100408.79 ms991,659.78 ms0
Angular 22205.9 ms (205.6, 218.8)549.2 ms (529.3, 586.3)100465.62 ms1001,888.36 ms16 ms
Blazor WebAssembly666.1 ms (642.2, 692.2)3,031 ms (2,967.9, 3,055.7)72 (70, 76)2,947.58 ms34 (34, 36)16,211.8 ms1,526 ms
Blazor WebAssembly, AOT683.4 ms (619.6, 764.3)5,050.9 ms (4,987.2, 5,089.3)68 (65, 68)4,550.22 ms3226,703.57 ms1,335 ms
Blazor Server209.5 ms (129.3, 216.1)1,160.6 ms (1,109, 1,302.3)100 (94, 100)485.93 ms99 (97, 99)1,955.21 ms3 ms
Bar chart of time to the first task row at 100 ms round trip: reference 565 ms, React 554 ms, Angular 549 ms, Blazor WebAssembly 3,031 ms, Blazor WebAssembly AOT 5,051 ms, Blazor Server 1,161 ms
Time to the first task row on a cold load with Chrome emulating 100 ms round trip and 10 Mbit/s. The whisker is the range of five runs.
Bar chart of Lighthouse mobile performance scores: reference 99, React 99, Angular 100, Blazor WebAssembly 34, Blazor WebAssembly AOT 32, Blazor Server 99
Lighthouse 13 performance score, mobile preset with simulated throttling, median of five runs on /lists/1.

Unthrottled on a fast PC, the three JavaScript clients and Blazor Server all show the first row in about 210 ms; Blazor WebAssembly needs 666 ms to download and start the runtime. At 100 ms round trip the gap opens: half a second for the small clients, 1.2 s for Blazor Server (the circuit handshake comes after the page), 3.0 s for WebAssembly and 5.1 s for its AOT build, which is bigger, not faster, at startup. Lighthouse's mobile preset, which simulates a slow phone on a slow link, puts WebAssembly at 34 with 1.5 s of total blocking time and a 16-second largest paint; desktop gives it 72. Every other client scores 99 or 100 in both presets.

C. Interactions on the 10,000-row list

Input to paint on Archive 10k, unthrottled, median of 5 runs (ms): selecting the list, one search keystroke, toggling a checkbox, opening the panel, sorting by title, select all, and frames per second while scrolling 40 wheel ticks
ClientSelect listPer keyTogglePanel openSort titleSelect allScroll fps
Reference109.612.116.215.626.713.759.97
React 19126.517.116.733.42918.656.52
Angular 22150.122.317.932.227.533.256.41
Blazor WebAssembly1,302.783.436.26884.548.256.33
Blazor WebAssembly, AOT1,530.731.916.827.42422.156.29
Blazor Server131.73638.34130.833.156.15
The same interactions at 100 ms round trip and 10 Mbit/s (ms, median of 5), plus clearing the search
ClientSelect listPer keyTogglePanel openSort titleClear search
Reference760.213.813.916.726.616.6
React 19785.618.516.734.732.620.3
Angular 22802.520.817.433.227.734.5
Blazor WebAssembly1,623.179.635.969.482.669.8
Blazor WebAssembly, AOT1,536.32916.526.723.923.1
Blazor Server469.4135160.7164157.6392.3
Grouped bar chart of input-to-paint times on the 10,000-row list, unthrottled, for five interactions and six clients
Input to paint, unthrottled. Selecting the list includes downloading and rendering 10,000 tasks; the other bars are single interactions on the loaded list.
Grouped bar chart of input-to-paint times on the 10,000-row list at 100 ms round trip for five interactions and six clients
The same at 100 ms round trip. Blazor Server's bars grew by far the most, and still understate a real link (see below).

Unthrottled, React and Angular sit within a few milliseconds of the baseline on every interaction. Blazor WebAssembly without AOT is the slow one: 83 ms per search keystroke against 17 for React, 1.3 s to select and render the list against 127 ms, a visible pause on every sort. AOT compilation fixes the interactions almost entirely (32 ms per key, 17 ms toggle, 27 ms panel) at the price of the 4.7 MB download above, and still takes 1.5 s to select the list. Blazor Server is React-like unthrottled, because the server is on the same machine.

At 100 ms round trip the picture flips for Blazor Server: a checkbox toggle takes 161 ms, a keystroke 135 ms, clearing the search 392 ms, because every one of them is a trip to the server and back. Selecting the list is faster than anyone else's 760 ms, since the browser never downloads the 10,000 rows. One caveat, measured rather than assumed: Chrome's network emulation delayed the WebSocket frames by about 40 ms per round trip, not the 100 ms it added to HTTP (a direct check: an HTTP fetch went from 7.05 ms to 248.25 ms, a Blazor click-to-render round trip from 7.15 ms to 46.95 ms). So Blazor Server's 100 ms rows are a floor; on a real 100 ms link every one of them is worse.

D. Memory

Archive 10k loaded, after a forced garbage collection, median of 3 runs. Not the tab's whole process memory.
ClientJS heap usedDOM nodesWebAssembly linear memory
Reference4,860,8241,321none
React 195,987,6201,207none
Angular 227,263,4242,716none
Blazor WebAssembly4,684,8601,90058,064,896
Blazor WebAssembly, AOT8,463,3921,90058,064,896
Blazor Server2,883,1246,327none
Bar chart of JavaScript heap plus WebAssembly memory on the 10,000-row list: reference 4.6 MB, React 5.7 MB, Angular 6.9 MB, Blazor WebAssembly 59.8 MB, AOT 63.4 MB, Blazor Server 2.7 MB
JavaScript heap used plus the WebAssembly linear memory where there is one. Blazor WebAssembly keeps the 10,000 tasks in a 58 MB .NET heap, not in JavaScript.

Blazor WebAssembly's JavaScript heap is the smallest of the four because the data lives in a 58 MB WebAssembly heap instead. Blazor Server keeps the least in the browser and the most in the DOM: 6,327 nodes, because its Virtualize overscan of 50 rows renders 117 rows at a time. On the server, the published process grew from 91 MB working set with no browser to 122 MB with one circuit on Archive 10k and 229 MB with ten, about 11 MB per open tab.

E. Developer metrics

Cold production build (median of 3, range), dev server to first 200, application code written by the sessions, direct and total dependencies, install size, and the 61-test suite's run time
ClientCold buildDev startApp linesSource filesDeps direct / totalInstall sizeSuite run
Reference829 ms (786, 1,408)1,060 ms1,13352 / 7368.0 MB44 s
React 191,017 ms (978, 4,322)1,052 ms1,378610 / 59104.4 MB67.8 s
Angular 223,347 ms (3,319, 3,440)3,277 ms1,6091915 / 365230.9 MB56.3 s
Blazor WebAssembly29,163 ms (26,773, 34,137)3,614 ms1,600255 / 3181.6 MB117.0 s
Blazor Server4,250 ms (4,145, 4,772)2,954 ms1,433251 / 13.2 MB50.4 s
Bar chart of cold production build times: reference 0.8 s, React 1.0 s, Angular 3.3 s, Blazor WebAssembly 29.2 s, Blazor Server 4.3 s
Cold production build, median of three, with bin, obj, dist and the Vite cache moved away before each one. React's TypeScript build cache was not, so its second and third builds are warm.
Bar chart of application code lines: reference 1,133, React 1,378, Angular 1,609, Blazor WebAssembly 1,600, Blazor Server 1,433
Non-blank lines of the app's own .razor, .cs, .ts, .tsx, .html and .js files, stylesheets left out. The sessions wrote all of it.

Three honest notes on this table. React's first cold build took 4.3 s and the next two about 1 s, because the TypeScript build cache lives in node_modules and the harness did not move it, so 1,017 ms flatters React a little. Blazor Server's dev start is the first prerendered page, which includes the server's own API call; the others are a static index.html. The two Blazor rows were first measured while another process on the PC was running a .NET build and test, so both were rerun on a quiet machine: WebAssembly's builds came out slower, 33,742 ms (33,188, 33,957), Blazor Server's 5,301 ms (4,234, 6,467), and the suite 158 s and 49 s. The first runs stand because they used the same protocol as the other three clients; the reruns say the overlap did not flatter Blazor. The 29-second WebAssembly publish is the IL linker trimming the runtime and the SDK compressing 94 files; the AOT publish added about two minutes on top.

Lines of code are closer than the folklore says: React's 1,378 lines sit mostly in one file, Angular's 1,609 in 19, the two Blazor apps' 1,600 and 1,433 in 25 each. Blazor Server has one dependency, the framework; Angular has 15 direct and 365 in all, and a 231 MB node_modules.

F. Accessibility

axe-core 4.14 on four states: the first list, the panel open, the shortcut dialog, Archive 10k
ClientViolations
Reference and Reactcolor-contrast (serious, 1 node) in every state except the shortcut dialog
Angularcolor-contrast (serious, 1), landmark-one-main (moderate, 1), region (moderate, 3) in every state except the shortcut dialog
Blazor WebAssemblycolor-contrast (serious, 1), label-content-name-mismatch (serious, 2) in all four states
Blazor Servercolor-contrast (serious, 1) in all four states

The one contrast violation is the shared stylesheet's, so every client has it and the reference does too. Angular's sessions never gave the page a main landmark; Blazor WebAssembly's gave two controls an accessible name that does not contain their visible text. None of the four was asked for more than the spec's baseline, and none did more.

G. How well the AI built each

From the session transcripts: Claude Code 2.1.287, Opus 5.5, one session per step, the harness's verdict after each. Flake retries are run-2 sessions that changed nothing and passed; their turns and meter are not in the totals.
ClientSessionsFirst-time passesRetries with a code changeVerdict flakesHand fixesTurnsMeterWallDenied commandsDependencies added
Blazor WebAssembly1614 of 15010333$9.0363 min340
Blazor Server1515 of 15000378$10.3853 min350
React 191713 of 15020319$8.8638 min271
Angular 221614 of 15100390$10.0868 min440

No client needed a hand fix, and the differences are small. React was the cheapest and the quickest; Angular took the most turns and the most denied shell commands (a cost of the harness, not of Angular: every denial is Claude Code refusing a chained or out-of-folder command). Blazor Server passed every step first time, but its step 1 alone cost 85 turns and $2.35 while the model learned that a prerendered page is visible before its circuit is alive. The failures that cost a verdict were timing, not framework errors: a test reading a row in the instant it disappeared (React, fixed by an optimistic update in step 7), Angular's zoneless redraw arriving one frame late (fixed by a change-detector call in the step 9 retry), and three retries that found nothing to fix. The Blazor, React and Angular posts walk every session.

H. What forty-five minutes in a browser found

The suite passes on all four. A person using each build for ten minutes found what a suite cannot. In Angular, the detail panel is never on screen: the router-outlet element takes a column of the shared grid, the main column shifts right, and the panel lands below the viewport on a page that does not scroll. Every panel test passed because the element exists and has a size. In Blazor WebAssembly, pressing Enter in the subtask box adds nothing and drops focus, and the tag box needs you to leave it and come back; the session that built step 10 had seen the same symptom and called it a one-in-fifty flake. In Blazor Server, clicking another list within a second of clearing the search loses the click, because the server's delayed URL update for the search wins. React showed nothing in the pass. The full log, with what was not tried by hand, is in the evidence.

The Angular client at 1366 by 768 with a task selected: the sidebar, an empty gap, the task list shifted to the right, and no detail panel visible
Angular with the first task selected, Chrome 156 at 1366 by 768. The gap after the sidebar is the router-outlet element; the panel is below the fold. Real screenshot, uncropped.

The seven hypotheses, settled

  1. Blazor WebAssembly's first-load transfer is at least five times React's. True, by a wide margin: 2,461,774 bytes against 87,780, twenty-eight times.
  2. Unthrottled, input-to-paint on the 10,000-row list is within a factor of two across WebAssembly, React and Angular. False. A keystroke is 83.4 ms in WebAssembly against 17.1 in React, a toggle 36.2 against 16.7, selecting the list 1,302.7 against 126.5. With AOT it becomes true for everything but selecting the list.
  3. Blazor Server's latency is the round trip plus a constant, slowest at 100 ms on every interaction, fastest first paint. Half true. It is the slowest on every interaction at 100 ms except selecting the list, where it is the fastest because the browser never downloads the rows; and its first paint is not the fastest: 485.93 ms desktop LCP against React's 408.79, 1,654 ms mobile FCP against 752.
  4. The AI needs the fewest retries and hand fixes for React and the most for Blazor Server. False. Nobody needed a hand fix; Blazor Server had zero retries and React two flaky verdicts. Blazor Server's trap showed up as turns inside one step, not as retries.
  5. Angular produces the most files and lines; Blazor WebAssembly the fewest lines. Mostly false. Angular has the most lines by nine, 1,609 against WebAssembly's 1,600, but not the most files (19 against 25), and WebAssembly is nowhere near the fewest lines: React is, with 1,378.
  6. Lighthouse mobile: React and Angular 90 or more, WebAssembly below 70, Blazor Server above 90. True: 99, 100, 34 and 99.
  7. The AI's most common mistake is wrong or missing test-id names. False. Not one verdict failed on a test id. The mistakes were timing: an optimistic update missing, a zoneless redraw a frame late, a circuit round trip the model did not expect.

What to pick

An existing .NET team shipping an intranet app: Blazor Server. The bytes are small, the first paint is React-fast, the round-trip cost is invisible on a LAN, memory is about 11 MB per open tab on the server, and the AI built it without a single retry once it had learned the prerender rule. Teach it that rule on day one.

A public site where phones and slow links matter: React or Angular. They are within a few percent of a no-framework page on every startup and interaction number here, and Lighthouse mobile agrees. Between the two, this comparison found no performance reason to choose; Angular gave the AI more structure and more turns, React gave it fewer turns and one big file. Decide on team and tooling, not on these tables.

A .NET team that wants C# in the browser: Blazor WebAssembly, with AOT if interactions matter more than the first load, and only where a 2.5 to 4.7 MB download and a 3 to 5 second start on a slow link are acceptable: a line-of-business app people open once a day, not a landing page. Budget the 29-second publish.

A student: build the reference client first. It is 1,133 lines, passes the same 61 tests, and every framework number above is a distance from it.

What these numbers do not decide

One app, one PC, one model, one week, one run of five. The 10,000-row list is one virtualisation per client, chosen by the AI; a different choice moves the interaction rows. The Blazor Server 100 ms rows are a floor, not a measurement of a real link. Lighthouse mobile simulates a phone; no phone was used. Developer metrics measure a machine, not a team. And four AI-built apps say something about Claude Code with each framework in November 2026; they say less about what an expert would write by hand.

The meter: 64 metered sessions, about $38 at list price for the four builds, plus the dry run and the retry sessions that changed nothing. Claude Code's own total_cost_usd; on a Max plan a measure, not a bill. The harness, the measurement pass and these posts were desktop sessions, unmetered and not counted.

Disclosure: this site runs on Blazor, static server-side rendering, and the author writes .NET for a living. The harness, the frozen suite and the pre-registered hypotheses exist so that this paragraph does not matter. Three of the seven hypotheses were wrong, and not all in Blazor's favour.

Checkpoint: every table above is in the repository's results page with the name of the raw file it came from, and the four clients are there with their per-step commits.

Do it yourself

Rerun one metric on your own machine and compare your median with the table.

  1. Clone the repository and start the API for the reference client:
    git clone https://github.com/rahulvyas777/coder000-todo-four-ways.git
    cd coder000-todo-four-ways
    dotnet run --project api -- --urls http://localhost:16050 --db run/reference.db
  2. Build the reference client (npm install then npm run build in reference), precompress its dist folder with harness/measure/precompress.js, and serve it with hosting/StaticHost on port 16052.
  3. Install the measurement scripts once (npm install in harness/measure) and run payload.js, lighthouse.js and interact.js against http://localhost:16052 with five runs; each script's header says its arguments.
  4. Repeat for any client in clients/ with its build command from harness/clients.json; Blazor Server runs from its publish folder instead of the static host.
  5. Run the suite against the same host to confirm the build you measured passes all 61 tests.
  6. Compare medians, not single runs, and note your Chrome version and whether anything else was running.

What you should see: the same order in every table, with your own machine's absolute numbers.

Frequently asked

Is Blazor slower than React?
Blazor WebAssembly is: 2.46 MB on the wire against React's 88 KB, 3.0 seconds to the first row on a 100 ms link against 0.55, and 83 ms per search keystroke on a 10,000-row list against 17 without AOT. Blazor Server loads as fast as React and then pays a server round trip per interaction, 161 ms for a checkbox at 100 ms. React and Angular are within a few percent of a no-framework page on every number here.
Does AOT compilation make Blazor WebAssembly fast?
It fixes the interactions: 32 ms per keystroke, 17 ms per toggle, close to React. It makes startup worse: 4.7 MB on the wire, 5.1 seconds to the first row on a 100 ms link, Lighthouse mobile 32. Use it when people keep the app open, not when they arrive from a search result.
Which framework did the AI build best?
All four passed the same 61 tests with no hand fix. React took the fewest turns and dollars, Blazor Server passed every step first time, Angular needed one real retry for a zoneless redraw bug, and Blazor WebAssembly lost one verdict to a flaky test. The use pass found a layout bug in Angular, an Enter-key bug in Blazor WebAssembly and a lost click in Blazor Server, and nothing in React.

This is the last part. The method is in Part 1, the builds in Parts 2 to 4, and the repository holds everything else.