Add route code splitting and Vite vendor chunk splitting

This commit is contained in:
2026-04-20 19:53:56 +02:00
parent 27369b43d6
commit 1d6564aa32
3 changed files with 91 additions and 50 deletions

View File

@@ -188,7 +188,7 @@ Issues are grouped by category and ordered roughly by severity. Each entry descr
--- ---
### TASK-010 — No code splitting: all pages bundled into the main chunk ### TASK-010 — No code splitting: all pages bundled into the main chunk (done)
**Where found:** `frontend/src/App.tsx` — all page imports are static (`import { DashboardPage } from "./pages/DashboardPage"`). `frontend/vite.config.ts` has no `build.rollupOptions.manualChunks`. **Where found:** `frontend/src/App.tsx` — all page imports are static (`import { DashboardPage } from "./pages/DashboardPage"`). `frontend/vite.config.ts` has no `build.rollupOptions.manualChunks`.

View File

@@ -19,7 +19,8 @@
* All unmatched paths redirect to `/`. * All unmatched paths redirect to `/`.
*/ */
import { FluentProvider } from "@fluentui/react-components"; import { lazy, Suspense } from "react";
import { FluentProvider, Spinner } from "@fluentui/react-components";
import { BrowserRouter, Navigate, Route, Routes } from "react-router-dom"; import { BrowserRouter, Navigate, Route, Routes } from "react-router-dom";
import { lightTheme } from "./theme/customTheme"; import { lightTheme } from "./theme/customTheme";
import { AuthProvider } from "./providers/AuthProvider"; import { AuthProvider } from "./providers/AuthProvider";
@@ -28,15 +29,16 @@ import { RequireAuth } from "./components/RequireAuth";
import { SetupGuard } from "./components/SetupGuard"; import { SetupGuard } from "./components/SetupGuard";
import { ErrorBoundary } from "./components/ErrorBoundary"; import { ErrorBoundary } from "./components/ErrorBoundary";
import { MainLayout } from "./layouts/MainLayout"; import { MainLayout } from "./layouts/MainLayout";
import { SetupPage } from "./pages/SetupPage";
import { LoginPage } from "./pages/LoginPage"; const SetupPage = lazy(() => import("./pages/SetupPage").then((m) => ({ default: m.SetupPage })));
import { DashboardPage } from "./pages/DashboardPage"; const LoginPage = lazy(() => import("./pages/LoginPage").then((m) => ({ default: m.LoginPage })));
import { MapPage } from "./pages/MapPage"; const DashboardPage = lazy(() => import("./pages/DashboardPage").then((m) => ({ default: m.DashboardPage })));
import { JailsPage } from "./pages/JailsPage"; const MapPage = lazy(() => import("./pages/MapPage").then((m) => ({ default: m.MapPage })));
import { JailDetailPage } from "./pages/JailDetailPage"; const JailsPage = lazy(() => import("./pages/JailsPage").then((m) => ({ default: m.JailsPage })));
import { ConfigPage } from "./pages/ConfigPage"; const JailDetailPage = lazy(() => import("./pages/JailDetailPage").then((m) => ({ default: m.JailDetailPage })));
import { HistoryPage } from "./pages/HistoryPage"; const ConfigPage = lazy(() => import("./pages/ConfigPage").then((m) => ({ default: m.ConfigPage })));
import { BlocklistsPage } from "./pages/BlocklistsPage"; const HistoryPage = lazy(() => import("./pages/HistoryPage").then((m) => ({ default: m.HistoryPage })));
const BlocklistsPage = lazy(() => import("./pages/BlocklistsPage").then((m) => ({ default: m.BlocklistsPage })));
/** /**
* Root application component — mounts providers and top-level routes. * Root application component — mounts providers and top-level routes.
@@ -46,48 +48,50 @@ function App(): React.JSX.Element {
<FluentProvider theme={lightTheme}> <FluentProvider theme={lightTheme}>
<ErrorBoundary> <ErrorBoundary>
<BrowserRouter future={{ v7_startTransition: true, v7_relativeSplatPath: true }}> <BrowserRouter future={{ v7_startTransition: true, v7_relativeSplatPath: true }}>
<AuthProvider> <Suspense fallback={<Spinner size="large" label="Loading…" />}>
<Routes> <AuthProvider>
{/* Setup wizard — always accessible; redirects to /login if already done */} <Routes>
<Route path="/setup" element={<SetupPage />} /> {/* Setup wizard — always accessible; redirects to /login if already done */}
<Route path="/setup" element={<SetupPage />} />
{/* Login — requires setup to be complete */} {/* Login — requires setup to be complete */}
<Route <Route
path="/login" path="/login"
element={ element={
<SetupGuard> <SetupGuard>
<LoginPage /> <LoginPage />
</SetupGuard> </SetupGuard>
} }
/> />
{/* Protected routes — require setup AND authentication */} {/* Protected routes — require setup AND authentication */}
<Route <Route
element={ element={
<SetupGuard> <SetupGuard>
<RequireAuth> <RequireAuth>
<TimezoneProvider> <TimezoneProvider>
<MainLayout /> <MainLayout />
</TimezoneProvider> </TimezoneProvider>
</RequireAuth> </RequireAuth>
</SetupGuard> </SetupGuard>
} }
> >
<Route index element={<DashboardPage />} /> <Route index element={<DashboardPage />} />
<Route path="/map" element={<MapPage />} /> <Route path="/map" element={<MapPage />} />
<Route path="/jails" element={<JailsPage />} /> <Route path="/jails" element={<JailsPage />} />
<Route path="/jails/:name" element={<JailDetailPage />} /> <Route path="/jails/:name" element={<JailDetailPage />} />
<Route path="/config" element={<ConfigPage />} /> <Route path="/config" element={<ConfigPage />} />
<Route path="/history" element={<HistoryPage />} /> <Route path="/history" element={<HistoryPage />} />
<Route path="/blocklists" element={<BlocklistsPage />} /> <Route path="/blocklists" element={<BlocklistsPage />} />
</Route> </Route>
{/* Fallback — redirect unknown paths to dashboard */} {/* Fallback — redirect unknown paths to dashboard */}
<Route path="*" element={<Navigate to="/" replace />} /> <Route path="*" element={<Navigate to="/" replace />} />
</Routes> </Routes>
</AuthProvider> </AuthProvider>
</BrowserRouter> </Suspense>
</ErrorBoundary> </BrowserRouter>
</ErrorBoundary>
</FluentProvider> </FluentProvider>
); );
} }

View File

@@ -35,4 +35,41 @@ export default defineConfig({
}, },
}, },
}, },
build: {
rollupOptions: {
output: {
manualChunks(id: string) {
if (id.includes("/node_modules/")) {
if (
id.includes("/node_modules/react/") ||
id.includes("/node_modules/react-dom/") ||
id.includes("/node_modules/react-router-dom/")
) {
return "react-vendor";
}
if (
id.includes("/node_modules/@fluentui/") ||
id.includes("/node_modules/@griffel/") ||
id.includes("/node_modules/@radix-ui/")
) {
return "ui-vendor";
}
if (id.includes("/node_modules/recharts/")) {
return "chart-vendor";
}
if (
id.includes("/node_modules/d3-") ||
id.includes("/node_modules/topojson-client/") ||
id.includes("/node_modules/world-atlas/")
) {
return "geo-vendor";
}
}
},
},
},
},
}); });