Task Board, Built Four Times

I built the same task board four times — React, Vue, Svelte, and Angular — using Claude Code, to see how the AI-assisted experience actually differs across frameworks. Same API, same features, four very different rides.

v0.1 — Initial pass

React

Tailwind CSS + shadcn/ui + Lucide React

Open the app →

Vue

Tailwind CSS + PrimeVue + PrimeIcons

Open the app →

Svelte

Tailwind CSS + Skeleton UI + Lucide Svelte

Open the app →

Angular

Angular Material + Material Icons

Open the app →

Findings & Comparison

Everything below is pulled straight from my build notes (docs/COMPARISON.md in the repo, alongside each app's own FEEDBACK.md) — same facts and numbers, just easier to skim than raw markdown.

Ratings summary

Here's how each one actually went, on a first pass:

Metric React Vue Svelte Angular
Ran easily 5/5 5/5 1/5 5/5
Understood the code pending pending pending pending
Folder structure made sense 4/5 4.5/5 5/5 3.5/5
Weekly usage delta +2% +1% +1% +1%

Stack per app

React Vue Svelte Angular
UI library shadcn/ui PrimeVue Skeleton UI Angular Material
Icons Lucide React PrimeIcons Lucide Svelte Material Icons
Data fetching TanStack Query TanStack Query TanStack Query HttpClient + RxJS + signals

Cross-cutting findings

Two things bit me on literally every app, not just one:

The API doesn't send CORS headers, period

First thing I hit: the live API sends no CORS headers at all, so the browser flatly refuses to call it directly. I confirmed it by throwing an OPTIONS preflight at it and watching it come back 405 with nothing helpful in the response.

Fixed the same way in every app: proxy /api through the dev server (Vite's server.proxy for React/Vue/Svelte, proxy.conf.json for Angular), then swap in a Cloudflare Pages Function to do the same job once it's actually deployed.

The same API returns two different response shapes

columns and categories come back as a plain array. users and tasks switch to { data, pagination } the moment you pass limit or offset — same API, different shape depending on which endpoint and which query params you happen to use.

Every app's API client just normalizes both shapes to a plain array and moves on.

Notable findings

PrimeVue quietly went premium

Building the Vue app, I went to install PrimeVue and found out it's gone premium. primevue@latest is now 5.0.1, and its own npm description flipped from "open source" to "a premium UI library" — it also pulls in @primeui/license-manager, literally described as an "offline license verifier for PrimeUI and PrimeUI PRO." The repo's MIT LICENSE.md still technically covers it, but I wasn't going to gamble a free portfolio piece on a dependency that ships its own license-checking code.

Pinned PrimeVue to ^4.5.5 instead — still explicitly "open source," still dist-tagged v4-stable — with primeicons@^8.0.0 (just a CSS icon font, unaffected either way). Its theming package, @primevue/themes, turned out to be deprecated too, in favor of @primeuix/themes — switched to that instead of installing something already marked dead.

Svelte + TanStack Query v6: this one was rough

Svelte 5's new runes plus a brand-new TanStack Query v6 adapter (rewritten specifically for runes) turned out to be a noticeably shakier combination than anything React or Vue threw at me. First it was just weird — the board would sit there for multiple seconds before anything painted, no error, no explanation. So I did the obvious thing and restarted the dev server. A lot. Which, it turns out, quietly wrecked Codespaces' port-forwarding in the background — and then a second, completely different-looking failure showed up: the board wouldn't load at all through the forwarded browser URL, with nothing in the console or the terminal even hinting at why.

A real chunk of that app's build session went into chasing a bug that had nothing to do with the code. And I couldn't fully fix it myself — I could tell what was probably wrong, but I had no way to reach into Codespaces' forwarding daemon and fix it directly.

The actual fix was removing the stale forwarded port by hand, in VS Code's Ports panel — not something you'd guess. If you hit this cold, with zero error messages pointing anywhere useful, you'd be completely forgiven for just assuming the app was broken and giving up.

Angular 22 wants a newer Node than this repo has

Then Angular. I go to scaffold it with the latest CLI and hit a wall immediately: @angular/cli@latest is 22.1.6, and it wants Node ^22.22.3 || ^24.15.0 || >=26.0.0. This whole repo's devcontainer is pinned to Node 20 — shared across all four apps. Angular 22 just isn't going to run in it.

Dropped to Angular ^21.2.22 instead — the newest major still fine with Node 20 (^20.19.0 || ^22.12.0 || >=24.0.0). Same call I made all session with TypeScript (6.0.2 over 7.0.2) and PrimeVue (v4 over v5): take the newest version that doesn't fight what's already there. I deliberately left the shared devcontainer alone rather than bumping it myself — that's a bigger change affecting three apps that were already reviewed and signed off, so that call belongs to me, the project owner, not something to change unilaterally. Not an oversight. A tradeoff.

Deployment

Once everything was built, I actually shipped it — one combined Cloudflare Pages deploy, all four apps and this page under their own paths.

Three clean, one déjà vu

React, Vue, and Angular deployed to Cloudflare Pages and just worked, first try — no surprises. Svelte deployed successfully too, but hit the exact same "stuck on loading columns" flakiness in production that showed up back in dev, needing a few refreshes before it'd actually load.

This is the interesting part: it happened on a completely fresh environment — no Codespaces, no dev server, no port-forwarding layer anywhere in the picture. Which means the original dev-mode slowness probably wasn't just a Codespaces problem after all — there's likely something real going on in the app or query stack itself. Best guess right now: TanStack Svelte Query v6's rune-based query initialization on first mount. Still not confirmed.

Lessons learned

I forgot to spec a .gitignore upfront

node_modules/ and build artifacts got committed before I caught it and cleaned it up after the fact.

Next time: spell out .gitignore requirements in the brief itself, upfront — not patch it in after the first commit.