Skip to content

feat: add route-level code splitting with React.lazy and Suspense (Closes #469) - #587

Open
waterWang wants to merge 1 commit into
Protocol-Guild:mainfrom
waterWang:feat/469-code-splitting
Open

feat: add route-level code splitting with React.lazy and Suspense (Closes #469)#587
waterWang wants to merge 1 commit into
Protocol-Guild:mainfrom
waterWang:feat/469-code-splitting

Conversation

@waterWang

Copy link
Copy Markdown

Summary

Closes #469 — Add route-level code splitting with React.lazy() and Suspense.

All 20 page components were eagerly imported in App.tsx, so the initial bundle included every route. This change code-splits each page into its own chunk loaded on demand.

What changed

  • src/utils/lazyWithRetry.ts (new): a React.lazy wrapper that caches the import() promise and clears it on rejection, so a momentary chunk-load failure can be retried on the next render (via the ErrorBoundary reset) instead of permanently poisoning the lazy component.
  • src/components/RouteLoader.tsx (new): centered spinner + i18n "Loading…" text used as the Suspense fallback for every lazy route.
  • src/components/ErrorBoundary.tsx: now supports a function-form fallback (({ onReset }) => …) and an internal resetErrorBoundary that re-renders children — enabling a working "Try again" button after a chunk-load failure.
  • src/App.tsx: all 18 unique page components are now lazy-loaded via lazyWithRetry; each route element is wrapped in <Suspense fallback={<RouteLoader />}> inside an <ErrorBoundary>. The EmployerLayout shell stays eagerly imported so nested-route Suspense never bubbles to the root without a fallback. Route structure is unchanged.

Verification

  • npx tsc --noEmit — passes.
  • vite build — succeeds, and confirms per-route chunks:
dist/assets/Home-CZxR7r.js              2.8 kB │ gzip:  0.9 kB
dist/assets/PayrollScheduler-…          35.0 kB │ gzip:  9.6 kB
dist/assets/AdminPanel-…                53.4 kB │ gzip: 10.9 kB
dist/assets/EmployeeEntry-…            113.9 kB │ gzip: 40.6 kB
dist/assets/Forecasting-…              354.7 kB │ gzip: 106.1 kB

Each page now loads its own chunk on navigation; the initial index bundle no longer includes all 20 pages.

Acceptance criteria

  • Each route loads its chunk on demand (per-route chunks visible in build output)
  • Loading spinner shown during chunk load (RouteLoader fallback)
  • Error boundary catches chunk load failures with retry (lazyWithRetry clears the promise; ErrorBoundary function fallback exposes working onReset → "Try again")
  • Bundle size reduction visible in build output (12+ separate page chunks instead of one app bundle)

…oses Protocol-Guild#469)

Replace 20 eager page imports with React.lazy() + custom lazyWithRetry
wrapper that clears the chunk-load promise on failure so an ErrorBoundary
reset can retry the load. Each route gets a Suspense fallback (RouteLoader)
wrapped in an ErrorBoundary whose fallback shows a Try Again button that
resets the boundary state.

- lazyWithRetry utility: clears cached promise on import() rejection
- RouteLoader component: centered spinner with i18n loading text
- ErrorBoundary: supports function-form fallback for onReset injection
- App.tsx: 18 lazy page imports, LazyRoute helper, reorganized layout
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.

Add route-level code splitting with React.lazy and Suspense

1 participant