Frontend Bundle
Captured: 2026-05-15 · next build (Next.js 15.5.11, App Router) · branch claude/scale-4-frontend
This is a documentation deliverable for the 10x-scale push. It records the
current next build route/bundle picture and recommends concrete code-splitting
work. The splitting itself is backlog — not done in this round.
How this was captured
npm run build compiles cleanly but the prerender step fails locally without
Clerk env vars (Missing publishableKey). To get the route table, the build was
re-run with throwaway dummy Clerk keys (pk_test_…/sk_test_…) — those keys
are only passed inline to the local build process and are not committed. The
compile + type-check + chunking output is unaffected by which keys are used.
Shared baseline
First Load JS shared by all 102 kB
├ chunks/1255-*.js 45.7 kB
├ chunks/4bd1b696-*.js 54.2 kB
└ other shared chunks (total) 2.04 kB
Middleware 98.8 kBEvery route pays the 102 kB shared baseline. The two big shared chunks are the React/Next runtime + Clerk + TanStack Query — unavoidable common ground.
Heaviest routes (route-specific JS / First Load JS)
| Route | Route JS | First Load JS | Notes |
|---|---|---|---|
/app/admin/test-cycles/new | 29.9 kB | 207 kB | Cycle-creation wizard — heaviest route-specific chunk in the app |
/app/admin/triage | 17.9 kB | 166 kB | Cross-cycle triage board |
/app/test-cycles/[id]/issues/[issueId] | 18.5 kB | 213 kB | Issue detail (comments, payout pipeline, markdown) |
/app/admin/test-cycles/[id] | 16.4 kB | 242 kB | Admin cycle detail — highest First Load JS in /app |
/app/profile | 1.0 kB | 288 kB | Tiny route code but pulls a huge shared/profile graph |
/app/admin/users/[id] | 0.75 kB | 258 kB | Same pattern — small page, heavy imported graph |
/app/admin/events/[id]/edit | 2.7 kB | 230 kB | dnd-kit + form stack |
/app/account | 5.0 kB | 228 kB | |
/app/test-cycles/[id] | 11.5 kB | 221 kB | Tester cycle detail |
/app/admin/test-cycles/[id]/new (issues/new) | 10.1 kB | 191 kB | Issue intake form |
/app/leaderboard/testers | 10.7 kB | 181 kB | Recently-merged tester leaderboard |
The two cycle detail pages — the focus of the scale push — are 221 kB
(tester) and 242 kB (admin) First Load JS. The admin page is the single
heaviest /app route by First Load JS.
Findings
1. Admin-only code is NOT leaking into tester bundles ✅
This was the headline concern. Verified:
src/lib/tester-stats.ts(~936 lines) is imported only by two API routes (/api/admin/testers/leaderboard,/api/admin/testers/[userId]/stats). No client component imports it, so it never ships to a browser bundle at all — it stays server-side.- The tester leaderboard page (
/app/leaderboard/testers) is its own route segment. Its 10.7 kB of route code is loaded only when that URL is visited; it does not bleed into the cycle detail pages or tester dashboards.
So there is no admin→tester bundle leak today. The route-based code splitting the App Router gives for free is doing its job at the page boundary.
2. The real waste is within routes, not across them
The cycle detail pages bundle everything eagerly even though most of it is behind a tab the user may never open:
- Admin
/app/admin/test-cycles/[id](242 kB) eagerly imports, per tab:CycleDocsPanel(Docs),MembersPanel+CyclePayoutByTester(Members),VerificationGatePanel/PayoutRatesPanel/JoinPolicyPanel(Settings), the brief editor dialog, and the Issues triage stack. Only one tab is visible at a time, yet all of it is in the first chunk. - Tester
/app/test-cycles/[id](221 kB) has the same shape:CycleDocsPanel,RunPackPanel,MarkdownView, and the Issues list all load up front.
3. recharts is dead weight
src/components/ui/chart.tsx wraps recharts (a heavy charting lib) but no
component imports ui/chart anywhere in the app. Tree-shaking should drop it,
but the dependency is still installed and a stray future import would silently
add a large chunk. Recommend removing recharts + ui/chart.tsx if charts
aren't on the roadmap (out of scope for this agent — flagged for cleanup).
4. Two date libraries are bundled
Both date-fns (v4) and dayjs are dependencies. date-fns is used by exactly
one client component (DateTimePicker.tsx); dayjs is used broadly across
profile/events/teams components. Standardising on one (likely dayjs, the
smaller + more-used one) would shave a chunk. Most cycle-detail date formatting
already uses the native Date API, so the cycle pages aren't directly affected —
this is an app-wide cleanup.
5. markdown.ts is clean
src/lib/markdown.ts is a tiny hand-rolled markdown→HTML renderer with zero
imports — it does not pull remark/rehype into client bundles. remark*
deps are server-only. No action needed.
Recommendations (BACKLOG — not done this round)
Priority order for a follow-up code-splitting pass:
- Lazy-load non-default cycle tabs. Wrap
CycleDocsPanel, the Members-tab panels, and the Settings-tab panels innext/dynamic(() => import(…), { ssr: false })on both cycle detail pages. The Issues tab is the common landing tab and should stay eager; everything else loads on tab activation. Estimated First Load JS reduction: ~40–70 kB on the admin cycle page. - Lazy-load the brief editor dialog (
CycleBriefSidePanel/ brief editor) — it's only opened on an explicit "Open editor" click, so it has no business in the first chunk. - Code-split
/app/admin/test-cycles/new(29.9 kB) — the heaviest route chunk. The multi-step wizard's later steps can be dynamically imported. - Remove
recharts+ui/chart.tsxif charting is not planned. - Consolidate
date-fns→dayjs(or vice-versa) app-wide; drop the unused one frompackage.json. @tanstack/react-virtualwas added this round for list virtualization. It is small (~5 kB) and loads only on the cycle detail routes that importVirtualizedBucketList— acceptable, no split needed.
What changed this round (for context)
This branch added @tanstack/react-virtual and VirtualizedBucketList, which
virtualizes the issue list on both cycle detail pages past ~100 issues. That
reduces DOM node count and render/layout cost at scale (500+ issues) but does
not change the JS bundle materially — virtualization is a runtime win, not a
bundle-size win. The bundle-size wins above are still open.