Skip to content

Split every non-critical route with React.lazy() (#421) - #474

Merged
haksungjang merged 2 commits into
mainfrom
feature/421-frontend-code-splitting
Sep 10, 2026
Merged

Split every non-critical route with React.lazy() (#421)#474
haksungjang merged 2 commits into
mainfrom
feature/421-frontend-code-splitting

Conversation

@haksungjang

Copy link
Copy Markdown
Contributor

Summary

  • router.tsx statically imported all ~30 screens, so the entry chunk carried every screen a visit could reach whether or not it did: 1,828.77 kB raw / 502.48 kB gzip.
  • Every route except the four auth pages and the dashboard now loads behind React.lazy(), wrapped in a single <Suspense> with a skeleton-based RouteLoadingFallback (this design system uses skeletons, not spinners, for top-level loading states).
  • Entry chunk is down to 949.90 kB raw / 295.16 kB gzip (~43% smaller gzipped).
  • Adds scripts/bundle-size-budget.mjs, wired into the frontend-bundle-audit CI job, failing the build if the entry chunk exceeds 400 KB gzip (measured floor ~288-295 KB, budget leaves headroom for legitimate growth in the still-eager pages).
  • titleCoverage.test.ts's router-source parser now also recognizes the const X = lazy(() => import(...)) shape, not just static imports.

Test plan

  • npm run typecheck, npm run lint, npm run i18n:check all clean
  • npx vitest run: 240 files / 2670 tests pass
  • node tools/em-dash/lint.mjs --base origin/main: clean
  • Mutation-tested the bundle budget check (lowered to 100 KB, confirmed it fails, restored) and the titleCoverage.test.ts lazy-import regex (removed it, confirmed the same 2 tests fail with "could not find a source file", restored)
  • Built with npm run build and served with vite preview; verified in a real browser that /login loads with no console errors and the entry chunk (index-B0zo_VGF.js) fetches with a 200
  • Full click-through of the ~28 newly-lazy authenticated routes was not feasible in this environment: the preview server has no backend to authenticate against, so only the public/eager surface could be exercised directly. The typecheck/test/mutation evidence above covers the authenticated routes' code paths instead.

🤖 Generated with Claude Code

https://claude.ai/code/session_01Q44qCG1TEkZxDo9cb9voYT

haksungjang and others added 2 commits September 11, 2026 00:42
router.tsx statically imported all ~30 screens, so the entry chunk
carried every screen whether or not a visit ever reached it (1,828.77
kB raw / 502.48 kB gzip). Only the four auth pages and the dashboard
stay eager now; everything else loads behind a single Suspense
boundary with a skeleton fallback. Entry chunk is down to 949.90 kB
raw / 295.16 kB gzip.

Adds a CI budget check (400 KB gzip) on the entry chunk so a future
regression fails the build instead of silently regrowing it.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Q44qCG1TEkZxDo9cb9voYT
CI's function-coverage gate dropped to 79.97% after #421: each route's
React.lazy(() => import(...).then(...)) adds two callbacks that only
run when a test navigates to that specific route, so router.tsx's own
function coverage just tracks how many of ~26 routes a test visited,
not whether the route table is correct. That's already covered by
titleCoverage.test.ts and visualCoverage.test.ts, which parse this
file's source directly, and the screens it wires up carry their own
coverage. Same treatment as main.tsx, already excluded for the same
reason.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Q44qCG1TEkZxDo9cb9voYT
@haksungjang
haksungjang merged commit 74c457f into main Sep 10, 2026
25 checks passed
@haksungjang
haksungjang deleted the feature/421-frontend-code-splitting branch September 10, 2026 16:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant