{"id":51254585,"url":"https://github.com/bejamas/ui-benchmark","last_synced_at":"2026-06-29T09:02:54.503Z","repository":{"id":343191786,"uuid":"1176663990","full_name":"bejamas/ui-benchmark","owner":"bejamas","description":null,"archived":false,"fork":false,"pushed_at":"2026-03-16T19:48:01.000Z","size":2884,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-03-17T06:32:53.747Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Astro","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/bejamas.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-03-09T08:50:08.000Z","updated_at":"2026-03-16T19:48:04.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/bejamas/ui-benchmark","commit_stats":null,"previous_names":["bejamas/ui-benchmark"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/bejamas/ui-benchmark","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bejamas%2Fui-benchmark","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bejamas%2Fui-benchmark/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bejamas%2Fui-benchmark/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bejamas%2Fui-benchmark/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/bejamas","download_url":"https://codeload.github.com/bejamas/ui-benchmark/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bejamas%2Fui-benchmark/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34919884,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-29T02:00:05.398Z","response_time":58,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-06-29T09:02:53.570Z","updated_at":"2026-06-29T09:02:54.481Z","avatar_url":"https://github.com/bejamas.png","language":"Astro","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Benchmark: Astro + b/ui vs Next.js + shadcn/ui\n\n\u003e Same marketing page. Same components. Three approaches. Radically different JavaScript footprints.\n\nA controlled comparison of JavaScript footprint and performance between three\nidentical marketing pages built with different component libraries.\n\n---\n\n## TL;DR\n\n| Metric | Astro + b/ui | Astro + React + shadcn | Next.js + shadcn | \n|---|---|---|---|\n| **JS bundle (gzipped)** | **22.80 KB** | 119.88 KB | 219.22 KB |\n| **JS bundle (raw)** | **60.42 KB** | 358.35 KB | 729.83 KB |\n| **JS files** | **7** | 19 | 10 |\n| **vs b/ui (gzip)** | **1x** | 5.3x more | 9.6x more |\n| **Zero-JS components** | 7 of 13 | 3 of 13 | 0 of 13 |\n\n---\n\n## The Three Projects\n\n| | **Astro + b/ui** | **Astro + React + shadcn** | **Next.js + shadcn** |\n|---|---|---|---|\n| Framework | Astro 5 | Astro 5 + React | Next.js 16 |\n| Components | b/ui (data-slot) | shadcn/ui (Radix) | shadcn/ui (Radix) |\n| Styling | Tailwind CSS v4 | Tailwind CSS v4 | Tailwind CSS v4 |\n| Rendering | Static HTML | Per-section React islands | Static (SSG) |\n| Package Manager | Bun | npm | npm |\n\nThe **Astro + React + shadcn** version uses per-section islands — static shadcn components (Button, Card, etc.) are server-rendered to HTML with zero JS, while interactive components (NavigationMenu, Tabs, Accordion, etc.) each hydrate as separate `client:load` islands. This isolates the cost of React + Radix without Next.js overhead.\n\n---\n\n## The Page\n\nAll three projects render the **exact same marketing onepager** with these interactive sections:\n\n- **Header** — NavigationMenu with 2 dropdown menus, 4 items each\n- **Hero** — Badge + 2 Buttons\n- **Features** — 6 Cards, each with a Tooltip\n- **Pricing** — Tabs (Monthly/Yearly) switching between 2 pricing grids\n- **Long-form text** — 4 paragraphs with 6 inline HoverCards\n- **FAQ** — Accordion with 6 collapsible items\n- **Contact form** — 2 Selects, 3 Inputs, 1 Checkbox, Labels\n- **Footer** — Separator + link columns\n\n### Components used (13 total)\n\n| Component | Interactive? | Astro b/ui JS | Astro React + shadcn JS | Next.js JS |\n|---|---|---|---|---|\n| NavigationMenu | ✅ | 7.20 KB gzip | Island (5.70 KB) | Bundled in ~219 KB |\n| Select (×2) | ✅ | 3.67 KB gzip | Island (14.02 KB) | Bundled in ~219 KB |\n| HoverCard (×6) | ✅ | 2.29 KB gzip | Island (2.04 KB) | Bundled in ~219 KB |\n| Tooltip (×6) | ✅ | 1.98 KB gzip | Island (3.25 KB) | Bundled in ~219 KB |\n| Tabs | ✅ | 1.79 KB gzip | Island (4.70 KB) | Bundled in ~219 KB |\n| Accordion (×6) | ✅ | 1.17 KB gzip | Island (3.32 KB) | Bundled in ~219 KB |\n| Button | ❌ Static | **0 KB** | ⚠️ Partial zero-JS | Bundled in ~219 KB |\n| Card | ❌ Static | **0 KB** | ⚠️ Partial zero-JS | Bundled in ~219 KB |\n| Badge | ❌ Static | **0 KB** | ⚠️ Partial zero-JS | Bundled in ~219 KB |\n| Input | ❌ Static | **0 KB** | **0 KB** | Bundled in ~219 KB |\n| Label | ❌ Static | **0 KB** | ⚠️ Partial zero-JS | Bundled in ~219 KB |\n| Checkbox | ❌ Static | **0 KB** | **0 KB** | Bundled in ~219 KB |\n| Separator | ❌ Static | **0 KB** | **0 KB** | Bundled in ~219 KB |\n| | | | | |\n| *Shared runtime* | | *Astro (4.70 KB)* | *React + Radix + Astro (86.85 KB)* | *Included above* |\n| **Total** | | **22.80 KB** | **119.88 KB** | **219.22 KB** |\n\u003e In Astro + b/ui, **7 of 13 components ship zero JavaScript**.\n\u003e\n\u003e In Astro + React + shadcn with per-section islands, **3 components are fully zero-JS** (Input, Checkbox, Separator) and **4 more are partially zero-JS** — their instances outside islands (hero buttons, feature cards) render as pure HTML, but the same components also appear inside interactive islands (PricingTabs) where they still ship JS.\n\u003e\n\u003e In Next.js, every component contributes to the JS bundle.\n\n---\n\n## JS Bundle Breakdown\n\n### Astro + b/ui (7 files, 22.80 KB gzipped)\n\nEach interactive component ships its own small JS module. Static components ship nothing.\n\n| File | Raw | Gzipped |\n|---|---|---|\n| NavigationMenu (data-slot) | 20.43 KB | 7.20 KB |\n| Shared index (Astro runtime) | 12.55 KB | 4.70 KB |\n| Select (data-slot) | 9.24 KB | 3.67 KB |\n| HoverCard (data-slot) | 6.15 KB | 2.29 KB |\n| Tooltip (data-slot) | 5.09 KB | 1.98 KB |\n| Tabs (data-slot) | 4.01 KB | 1.79 KB |\n| Accordion (data-slot) | 2.95 KB | 1.17 KB |\n| **Total** | **60.42 KB** | **22.80 KB** |\n\n### Astro + React + shadcn (19 files, 119.88 KB gzipped)\n\nPer-section React islands. Each interactive section hydrates independently. Static components (Button, Card, etc.) are server-rendered to HTML outside of islands.\n\n| File | Raw | Gzipped |\n|---|---|---|\n| React/ReactDOM runtime | 178.42 KB | 56.24 KB |\n| ContactSelects island | 40.61 KB | 14.02 KB |\n| Radix shared utilities | 31.02 KB | 10.39 KB |\n| React shared utilities | 26.97 KB | 10.43 KB |\n| SiteNav island | 18.43 KB | 5.70 KB |\n| PricingTabs island | 16.07 KB | 4.70 KB |\n| Astro island runtime | 11.94 KB | 4.30 KB |\n| FAQAccordion island | 8.49 KB | 3.32 KB |\n| FeatureTooltip island | 8.94 KB | 3.25 KB |\n| InlineHoverCard island | 5.50 KB | 2.04 KB |\n| *9 smaller chunks* | 11.96 KB | 5.49 KB |\n| **Total** | **358.35 KB** | **119.88 KB** |\n\n### Next.js + shadcn (10 files, 219.22 KB gzipped)\n\nIncludes React runtime, Radix UI primitives, Next.js router, and framework code.\n\n| File | Raw | Gzipped |\n|---|---|---|\n| Main framework chunk | 219.37 KB | 68.50 KB |\n| Next.js router chunk | 154.61 KB | 39.30 KB |\n| Framework utilities | 109.96 KB | 38.70 KB |\n| Page/component code | 116.67 KB | 31.02 KB |\n| Hydration/RSC chunk | 72.37 KB | 24.60 KB |\n| *5 smaller chunks* | 56.85 KB | 17.10 KB |\n| **Total** | **729.83 KB** | **219.22 KB** |\n\n---\n\n## Where Does the JavaScript Come From?\n\n```\nAstro + b/ui          ████ 22.80 KB\n                      └─ 6 data-slot modules + Astro runtime\n\nAstro + React + shadcn ████████████████████████ 119.88 KB\n                       └─ React (56 KB) + 6 Radix islands (44 KB) + Astro (4 KB)\n\nNext.js + shadcn       ████████████████████████████████████████████ 219.22 KB\n                       └─ React (56 KB) + Radix/components (~54 KB)\n                          + Next.js router (39 KB) + Framework (70 KB)\n```\n\n### What the Astro + React version tells us\n\nEven with per-section islands (each interactive component as its own `client:load` boundary), the gzipped JS is **~120 KB** — slightly *more* than the single-island approach (~114 KB). Splitting into islands causes Radix utility code to duplicate across chunks.\n\n1. **React + Radix** costs **~120 KB gzip** — that's the floor for shadcn/ui, even with optimized islands\n2. **Next.js adds ~100 KB** more on top (router, RSC, framework utils)\n3. **b/ui's data-slot approach** achieves the same interactive UX for **23 KB total**, because it replaces both React AND Radix with vanilla JS primitives\n4. **Islands don't solve the React tax** — they help with selective hydration but can't eliminate the runtime cost\n\n---\n\n## Why This Happens\n\n### The hydration tax (React-based versions)\n\nIn both React-based versions, the browser must:\n1. **Download** React + ReactDOM (~56 KB gzipped)\n2. **Download** Radix UI primitives for each component\n3. **Parse and execute** all JavaScript\n4. **Hydrate** the entire component tree — even static text\n\nMarketing sites are content-first. They have long text, forms, tooltips, and\nnavigation — but they rarely need complex client-side state.\n\nIn Next.js + shadcn, **every text node on the page must hydrate** even though\nit never changes. The React runtime, Radix primitives, and reconciliation engine\nare all loaded for what is essentially a static page with a few interactive\nwidgets.\n\n### The framework tax (Next.js only)\n\nNext.js adds additional overhead on top of React:\n1. **Client-side router** — Links, prefetching, route transitions\n2. **RSC infrastructure** — Server/client component reconciliation\n3. **Framework utilities** — Error boundaries, loading states, metadata\n\nIn Astro + React + shadcn, removing Next.js cuts the JS in half — but you still\npay for React + Radix to hydrate the entire component tree.\n\n### The Astro + b/ui approach\n\n1. Static components → **pure HTML + CSS** (zero JavaScript)\n2. Interactive components → **data-slot primitives** (tiny vanilla JS)\n3. **No framework runtime**, no virtual DOM, no hydration\n4. JavaScript loaded **only for the 6 component types** that need it\n\n### Why b/ui Buttons inside Tabs are still zero-JS\n\nIn b/ui, the Tabs `data-slot` JS only **toggles visibility** of pre-rendered HTML panels. It doesn't know or care what's inside them — Buttons and Cards are already compiled to static HTML at build time. The JS shows/hides a `\u003cdiv\u003e`, and the Button inside is just a `\u003cbutton\u003e` DOM node.\n\nIn React, Tabs needs to **render** its children via `React.createElement()`. Button, Card, and Badge are function calls that must exist in the JS bundle. When you switch tabs, React re-renders the subtree — so it needs the component code for everything inside.\n\n**b/ui Tabs toggles HTML panels. React Tabs creates them.**\n\n---\n\n## How to Reproduce\n\n### Prerequisites\n\n- Node.js 18+\n- Bun 1.2+ (for Astro b/ui project)\n- npm (for Next.js and Astro React projects)\n\n### Build \u0026 Measure\n\n```bash\n# Build all three\ncd astro-bui \u0026\u0026 bun install \u0026\u0026 bun run build\ncd astro-react-shadcn \u0026\u0026 npm install \u0026\u0026 npx astro build\ncd nextjs-shadcn \u0026\u0026 npm install \u0026\u0026 npm run build\n\n# Measure\nnode scripts/measure.mjs\n```\n\n### Preview Production Builds\n\n```bash\n# Astro + b/ui (port 4321)\ncd astro-bui \u0026\u0026 bun run preview\n\n# Astro + React + shadcn (port 4322)\ncd astro-react-shadcn \u0026\u0026 npx astro preview --port 4322\n\n# Next.js (port 3000)\ncd nextjs-shadcn \u0026\u0026 npm run start\n```\n\n### Lighthouse Results (Mobile, Incognito)\n\n| Metric | Astro + b/ui | Astro + React + shadcn | Next.js + shadcn |\n|---|---|---|---|\n| **FCP** | 1.4 s | 1.6 s | 0.8 s |\n| **LCP** | **1.4 s** | 2.1 s | 2.6 s |\n| **TBT** | **0 ms** | **0 ms** | 40 ms |\n| **CLS** | 0.013 | 0.013 | 0 |\n| **Speed Index** | 1.4 s | 1.6 s | 0.8 s |\n\n\u003e **Note:** Next.js has the fastest FCP/Speed Index (0.8 s) because it pre-renders\n\u003e HTML immediately — but it has the worst LCP (2.6 s) because the browser must\n\u003e download, parse, and hydrate 219 KB of JavaScript before the page becomes fully\n\u003e painted. Astro + b/ui paints and finishes in the same 1.4 s with zero blocking time.\n\n### INP — Interaction to Next Paint (20× CPU throttle)\n\n| | Astro + b/ui | Astro + React + shadcn | Next.js + shadcn |\n|---|---|---|---|\n| **Worst INP** | ~100 ms | ~100 ms | 168 ms |\n\n\u003e All three are under the 200 ms \"good\" threshold, but Next.js interactions are\n\u003e ~70% slower due to React's reconciliation overhead on every state change.\n\n### Code Coverage (JS + CSS, after full interaction)\n\n| | Astro + b/ui | Astro + React + shadcn | Next.js + shadcn |\n|---|---|---|---|\n| **Total loaded** | 132 KB | 424 KB | 695 KB |\n| **Used** | 85.7 KB | 260 KB | 431 KB |\n| **Unused** | **46.1 KB** | 163 KB | 265 KB |\n\n\u003e Next.js ships **5.7× more unused code** than Astro + b/ui. Even after interacting\n\u003e with every component on the page, 265 KB of downloaded JavaScript is never executed.\n\n---\n\n## Methodology\n\n| Setting | Value |\n|---|---|\n| Astro version | 5.16.4 – 5.18.0 |\n| Next.js version | 16.1.6 (Turbopack) |\n| Build mode | Static (SSG) for all three |\n| Measurement | All `.js` files in build output, gzipped programmatically |\n| Viewport | 1280×800 |\n| Components | 13 identical types, same count and placement |\n| Next.js scope | `.next/static` (client-shipped JS only) |\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbejamas%2Fui-benchmark","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fbejamas%2Fui-benchmark","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbejamas%2Fui-benchmark/lists"}