Skip to content

perf: route-level code splitting, honest bundle budget, Lighthouse audit (M9) - #11

Merged
Amayyas merged 5 commits into
mainfrom
feat/m9-tests-optimisation
Jul 23, 2026
Merged

perf: route-level code splitting, honest bundle budget, Lighthouse audit (M9)#11
Amayyas merged 5 commits into
mainfrom
feat/m9-tests-optimisation

Conversation

@Amayyas

@Amayyas Amayyas commented Jul 23, 2026

Copy link
Copy Markdown
Owner

Final module — Tests & Optimisation (specification M9): "optimisation chargement Stockfish (lazy), tests hooks chess, audit Lighthouse performance/accessibilité".

Optimisation

  • Route-level code splitting: every route but the home page is now lazily loaded behind a Suspense fallback. The whole app used to ship as one bundle, so the landing page downloaded the board, react-chessboard and the engine wrapper that only the game modes need. Home stays eager so the Lighthouse-measured landing paint needs no extra chunk.
  • Initial JavaScript: ~212 kB → 155 kB gzipped. Mode chunks arrive only when their route is opened.
  • Honest size budget: the guard read the manifest's entry + static imports instead of summing every .js, so it measures the true first load. Budget returned to 200 kB (the value before M10 raised it to 230 for the Supabase client), with headroom at 155 kB. Lazy chunks are reported separately.
  • Stockfish was already lazy (the worker is only instantiated on first use), which the spec's mitigation asks for.

Tests

  • Added useLocalStorage coverage: initial-value fallback, stored reads, value/updater writes, and the two guarded paths (malformed JSON, failing write) that must degrade to in-memory state. The core chess hooks (useChessGame, useChessClock, usePuzzleSession, useHuntGame) were already covered.
  • 258 unit tests pass.

Lighthouse audit (local, after splitting)

Category Score Threshold
Performance 95 ≥ 90
Accessibility 100 ≥ 95
Best practices 100 ≥ 90
SEO 100 ≥ 90

Verification

  • Real-browser nav smoke against the production build: home loads only the entry chunk; opening Coach fetches its chunk (+ board, engine) on demand; no console or network errors.
  • npm run ci green; lhci autorun green.

Summary by CodeRabbit

  • New Features
    • Added lazy loading for most non-home routes.
    • Added a loading spinner while lazily loaded route content is being fetched.
    • Kept the home page available immediately.
  • Documentation
    • Clarified how initial JavaScript size budgets are calculated and how lazy chunks are reported separately.
  • Tests
    • Updated routing tests to wait for lazy-loaded content.
    • Added unit tests covering local storage behavior, including malformed data and storage write failures.
  • Chores
    • Improved bundle-size checks to classify initial versus lazily loaded code using Vite’s build manifest.

Amayyas added 4 commits July 23, 2026 08:31
The whole app shipped as one bundle, so opening the landing page still
downloaded the board, the react-chessboard library and the engine
wrapper that only the game modes need. Load each route lazily behind a
Suspense fallback, keeping just the home page eager so the landing paint
— the one Lighthouse measures — needs no extra chunk.

The initial JavaScript drops from about 212 kB to 155 kB gzipped; the
mode chunks now arrive only when their route is opened.
With the routes split out, the guard's old "every .js file" sum no
longer described the first load. Read the manifest instead and count
only the entry and its static imports, reporting the lazy route chunks
separately. The budget can now return to 200 kB — the promise made when
M10 raised it to 230 for the Supabase client — with real headroom at
155 kB.
Exercise the initial-value fallback, reads of stored values, value and
updater writes, and the two guarded paths — malformed JSON and a failing
write — that must degrade to in-memory state rather than break the page.
The size guard now measures the entry and its static imports from the
manifest; say so, so the table's "Initial JavaScript" is not read as the
whole app's JS.
@coderabbitai

coderabbitai Bot commented Jul 23, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 5390d782-a1f7-4216-935e-ea40c384d399

📥 Commits

Reviewing files that changed from the base of the PR and between 97c6b70 and 2c3b36d.

📒 Files selected for processing (1)
  • scripts/check-bundle-size.mjs
🚧 Files skipped from review as they are similar to previous changes (1)
  • scripts/check-bundle-size.mjs

📝 Walkthrough

Walkthrough

The app now lazy-loads non-home routes with a shared Suspense fallback. Vite emits a manifest used by bundle-size checks to separate initial assets from lazy chunks. Routing tests await lazy content, and useLocalStorage receives broader behavioral coverage.

Changes

Lazy route loading and bundle accounting

Layer / File(s) Summary
Lazy route loading and async route tests
src/App.tsx, src/components/Layout/RouteFallback.tsx, src/App.test.tsx
Non-home routes use React.lazy and Suspense with RouteFallback; routing tests await asynchronously rendered headings.
Manifest-driven bundle budgets
vite.config.ts, scripts/check-bundle-size.mjs, README.md
The Vite manifest identifies initial JavaScript and static imports, while CSS, Stockfish assets, and lazy route chunks are classified and reported separately.

Local storage hook validation

Layer / File(s) Summary
Local storage hook test coverage
src/hooks/useLocalStorage.test.ts
Adds tests for initialization, JSON parsing, direct and functional persistence, malformed data, cleanup, and storage write failures.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Router
  participant Suspense
  participant ReactLazy
  Router->>Suspense: render matched route
  Suspense->>ReactLazy: load route chunk
  ReactLazy-->>Suspense: provide page component
  Suspense-->>Router: render page or RouteFallback
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main changes: route-level code splitting, updated bundle budgeting, and Lighthouse validation.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/m9-tests-optimisation

Comment @coderabbitai help to get the list of available commands.

@Amayyas Amayyas self-assigned this Jul 23, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
src/App.tsx (1)

38-111: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Consider extracting a LazyRoute helper to remove the repeated Suspense boilerplate.

The <Suspense fallback={<RouteFallback />}>...</Suspense> wrapper is duplicated across all 8 lazy routes. A small helper cuts the repetition and makes adding future lazy routes less error-prone (e.g. forgetting the fallback).

♻️ Suggested refactor
+function LazyRoute({ Component }: { Component: React.ComponentType }) {
+  return (
+    <Suspense fallback={<RouteFallback />}>
+      <Component />
+    </Suspense>
+  )
+}
+
 export default function App() {
   ...
-        <Route
-          path={ROUTES.coach}
-          element={
-            <Suspense fallback={<RouteFallback />}>
-              <CoachPage />
-            </Suspense>
-          }
-        />
+        <Route path={ROUTES.coach} element={<LazyRoute Component={CoachPage} />} />

Apply the same pattern to the other lazy routes (wrap LeaderboardPage with RequireAuth around <LazyRoute Component={LeaderboardPage} />).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/App.tsx` around lines 38 - 111, Extract a LazyRoute helper in App.tsx
that accepts the lazy page component and renders it inside Suspense with
RouteFallback. Replace the repeated Suspense wrappers for the eight lazy routes,
including wrapping LazyRoute for LeaderboardPage with RequireAuth, while
preserving each route path and existing authentication behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@scripts/check-bundle-size.mjs`:
- Around line 68-71: Update the lazyJs filter to exclude files matching the
existing /stockfish/i pattern, so Stockfish chunks are counted only by the
stockfish budget group and not in the informational lazy route chunk count or
size. Keep the existing JavaScript and initialJs filtering behavior unchanged.

---

Nitpick comments:
In `@src/App.tsx`:
- Around line 38-111: Extract a LazyRoute helper in App.tsx that accepts the
lazy page component and renders it inside Suspense with RouteFallback. Replace
the repeated Suspense wrappers for the eight lazy routes, including wrapping
LazyRoute for LeaderboardPage with RequireAuth, while preserving each route path
and existing authentication behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 51c8678f-c60e-4bef-90b6-975e5b86e9dd

📥 Commits

Reviewing files that changed from the base of the PR and between acfb50d and 97c6b70.

📒 Files selected for processing (7)
  • README.md
  • scripts/check-bundle-size.mjs
  • src/App.test.tsx
  • src/App.tsx
  • src/components/Layout/RouteFallback.tsx
  • src/hooks/useLocalStorage.test.ts
  • vite.config.ts

Comment thread scripts/check-bundle-size.mjs
The engine worker is a lazily loaded .js outside the initial set, so it
slipped into the informational "lazy route chunk" count and size —
double counted against its own budget and mislabelled as a route.
Exclude it from that tally.
@Amayyas
Amayyas merged commit 60ac8c3 into main Jul 23, 2026
6 checks passed
@Amayyas
Amayyas deleted the feat/m9-tests-optimisation branch July 23, 2026 06:52
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