feat: Replaced 6 static page imports with React.lazy + Suspense in App.…

- "frontend/src/App.tsx"

GSD-Task: S05/T02
This commit is contained in:
jlightner 2026-04-03 23:06:39 +00:00
parent 39e169b4ce
commit 1bbcb8f5bf
4 changed files with 140 additions and 14 deletions

View file

@ -79,7 +79,7 @@ The 945-line `public-client.ts` monolith contains all API functions across 10 do
- Estimate: 1h
- Files: frontend/src/api/public-client.ts, frontend/src/api/client.ts, frontend/src/api/search.ts, frontend/src/api/techniques.ts, frontend/src/api/creators.ts, frontend/src/api/topics.ts, frontend/src/api/stats.ts, frontend/src/api/reports.ts, frontend/src/api/admin-pipeline.ts, frontend/src/api/admin-techniques.ts, frontend/src/api/auth.ts, frontend/src/api/index.ts
- Verify: cd frontend && npm run build && cd .. && ! rg 'public-client' frontend/src/ -g '*.ts' -g '*.tsx' && ls frontend/src/api/*.ts | wc -l
- [ ] **T02: Add React.lazy code splitting for admin and creator dashboard pages** — ## Description
- [x] **T02: Replaced 6 static page imports with React.lazy + Suspense in App.tsx, producing 7 JS chunks from the previous monolithic bundle** — ## Description
All 17 page components are eagerly imported in `App.tsx`. Wrap admin and creator pages in `React.lazy()` + `Suspense` so they load on-demand, reducing initial bundle size.

View file

@ -0,0 +1,36 @@
{
"schemaVersion": 1,
"taskId": "T01",
"unitId": "M019/S05/T01",
"timestamp": 1775257496151,
"passed": false,
"discoverySource": "task-plan",
"checks": [
{
"command": "cd frontend",
"exitCode": 0,
"durationMs": 8,
"verdict": "pass"
},
{
"command": "npm run build",
"exitCode": 254,
"durationMs": 101,
"verdict": "fail"
},
{
"command": "cd ..",
"exitCode": 0,
"durationMs": 4,
"verdict": "pass"
},
{
"command": "! rg 'public-client' frontend/src/ -g '*.ts' -g '*.tsx'",
"exitCode": 0,
"durationMs": 9,
"verdict": "pass"
}
],
"retryAttempt": 1,
"maxRetries": 2
}

View file

@ -0,0 +1,80 @@
---
id: T02
parent: S05
milestone: M019
provides: []
requires: []
affects: []
key_files: ["frontend/src/App.tsx"]
key_decisions: ["Suspense placed at each lazy route element rather than wrapping all routes, so eager pages never see the fallback", "ProtectedRoute wraps Suspense (not vice versa) so auth redirect happens before chunk load"]
patterns_established: []
drill_down_paths: []
observability_surfaces: []
duration: ""
verification_result: "npm run build exits 0 producing 7 JS chunks. rg confirms 6 React.lazy imports. All slice-level checks pass: no public-client references, api/index.ts and client.ts exist, 11 API module files."
completed_at: 2026-04-03T23:06:29.631Z
blocker_discovered: false
---
# T02: Replaced 6 static page imports with React.lazy + Suspense in App.tsx, producing 7 JS chunks from the previous monolithic bundle
> Replaced 6 static page imports with React.lazy + Suspense in App.tsx, producing 7 JS chunks from the previous monolithic bundle
## What Happened
---
id: T02
parent: S05
milestone: M019
key_files:
- frontend/src/App.tsx
key_decisions:
- Suspense placed at each lazy route element rather than wrapping all routes, so eager pages never see the fallback
- ProtectedRoute wraps Suspense (not vice versa) so auth redirect happens before chunk load
duration: ""
verification_result: passed
completed_at: 2026-04-03T23:06:29.631Z
blocker_discovered: false
---
# T02: Replaced 6 static page imports with React.lazy + Suspense in App.tsx, producing 7 JS chunks from the previous monolithic bundle
**Replaced 6 static page imports with React.lazy + Suspense in App.tsx, producing 7 JS chunks from the previous monolithic bundle**
## What Happened
Converted AdminPipeline, AdminTechniquePages, AdminReports, CreatorDashboard, CreatorSettings, and About from static imports to React.lazy() dynamic imports. Added LoadingFallback component and individual Suspense wrappers per lazy route. ProtectedRoute wraps Suspense for creator routes so auth guard fires before chunk load. Core public and auth pages remain eagerly imported.
## Verification
npm run build exits 0 producing 7 JS chunks. rg confirms 6 React.lazy imports. All slice-level checks pass: no public-client references, api/index.ts and client.ts exist, 11 API module files.
## Verification Evidence
| # | Command | Exit Code | Verdict | Duration |
|---|---------|-----------|---------|----------|
| 1 | `cd frontend && npm run build` | 0 | ✅ pass | 6400ms |
| 2 | `ls frontend/dist/assets/*.js | wc -l` | 0 | ✅ pass (7 files) | 100ms |
| 3 | `rg 'React.lazy' frontend/src/App.tsx` | 0 | ✅ pass (6 matches) | 100ms |
| 4 | `rg 'public-client' frontend/src/ -g '*.ts' -g '*.tsx'` | 1 | ✅ pass (no matches) | 100ms |
| 5 | `test -f frontend/src/api/index.ts && test -f frontend/src/api/client.ts` | 0 | ✅ pass | 50ms |
| 6 | `ls frontend/src/api/*.ts | wc -l` | 0 | ✅ pass (11 files) | 50ms |
## Deviations
None.
## Known Issues
None.
## Files Created/Modified
- `frontend/src/App.tsx`
## Deviations
None.
## Known Issues
None.

View file

@ -1,4 +1,4 @@
import { useState, useEffect, useRef, useCallback } from "react";
import React, { useState, useEffect, useRef, useCallback, Suspense } from "react";
import { Link, Navigate, Route, Routes, useLocation, useNavigate } from "react-router-dom";
import Home from "./pages/Home";
import SearchResults from "./pages/SearchResults";
@ -7,20 +7,30 @@ import CreatorsBrowse from "./pages/CreatorsBrowse";
import CreatorDetail from "./pages/CreatorDetail";
import TopicsBrowse from "./pages/TopicsBrowse";
import SubTopicPage from "./pages/SubTopicPage";
import AdminReports from "./pages/AdminReports";
import AdminPipeline from "./pages/AdminPipeline";
import AdminTechniquePages from "./pages/AdminTechniquePages";
import About from "./pages/About";
import Login from "./pages/Login";
import Register from "./pages/Register";
import CreatorDashboard from "./pages/CreatorDashboard";
import CreatorSettings from "./pages/CreatorSettings";
// Lazy-loaded pages — admin, creator, and info routes split into separate chunks
const AdminReports = React.lazy(() => import("./pages/AdminReports"));
const AdminPipeline = React.lazy(() => import("./pages/AdminPipeline"));
const AdminTechniquePages = React.lazy(() => import("./pages/AdminTechniquePages"));
const About = React.lazy(() => import("./pages/About"));
const CreatorDashboard = React.lazy(() => import("./pages/CreatorDashboard"));
const CreatorSettings = React.lazy(() => import("./pages/CreatorSettings"));
import AdminDropdown from "./components/AdminDropdown";
import AppFooter from "./components/AppFooter";
import SearchAutocomplete from "./components/SearchAutocomplete";
import ProtectedRoute from "./components/ProtectedRoute";
import { AuthProvider, useAuth } from "./context/AuthContext";
function LoadingFallback() {
return (
<div style={{ display: "flex", justifyContent: "center", alignItems: "center", minHeight: "40vh" }}>
<p style={{ color: "var(--text-secondary, #94a3b8)", fontSize: "0.95rem" }}>Loading</p>
</div>
);
}
function AuthNav() {
const { isAuthenticated, user, logout } = useAuth();
const navigate = useNavigate();
@ -163,20 +173,20 @@ function AppShell() {
<Route path="/topics" element={<TopicsBrowse />} />
{/* Admin routes */}
<Route path="/admin/reports" element={<AdminReports />} />
<Route path="/admin/pipeline" element={<AdminPipeline />} />
<Route path="/admin/techniques" element={<AdminTechniquePages />} />
<Route path="/admin/reports" element={<Suspense fallback={<LoadingFallback />}><AdminReports /></Suspense>} />
<Route path="/admin/pipeline" element={<Suspense fallback={<LoadingFallback />}><AdminPipeline /></Suspense>} />
<Route path="/admin/techniques" element={<Suspense fallback={<LoadingFallback />}><AdminTechniquePages /></Suspense>} />
{/* Info routes */}
<Route path="/about" element={<About />} />
<Route path="/about" element={<Suspense fallback={<LoadingFallback />}><About /></Suspense>} />
{/* Auth routes */}
<Route path="/login" element={<Login />} />
<Route path="/register" element={<Register />} />
{/* Creator routes (protected) */}
<Route path="/creator/dashboard" element={<ProtectedRoute><CreatorDashboard /></ProtectedRoute>} />
<Route path="/creator/settings" element={<ProtectedRoute><CreatorSettings /></ProtectedRoute>} />
<Route path="/creator/dashboard" element={<ProtectedRoute><Suspense fallback={<LoadingFallback />}><CreatorDashboard /></Suspense></ProtectedRoute>} />
<Route path="/creator/settings" element={<ProtectedRoute><Suspense fallback={<LoadingFallback />}><CreatorSettings /></Suspense></ProtectedRoute>} />
{/* Fallback */}
<Route path="*" element={<Navigate to="/" replace />} />