diff --git a/frontend/src/app/router.tsx b/frontend/src/app/router.tsx
index 32f6278..e3f0158 100644
--- a/frontend/src/app/router.tsx
+++ b/frontend/src/app/router.tsx
@@ -1,30 +1,73 @@
+import { lazy, Suspense } from "react";
import { createBrowserRouter, Navigate } from "react-router-dom";
import { AppShell } from "@/layouts/AppShell";
-import { DashboardPage } from "@/pages/DashboardPage";
-import { DocumentsPage } from "@/pages/DocumentsPage";
-import { IngestionJobsPage } from "@/pages/IngestionJobsPage";
-import { SearchPage } from "@/pages/SearchPage";
-import { DocumentViewerPage } from "@/pages/DocumentViewerPage";
-import { TablesFiguresPage } from "@/pages/TablesFiguresPage";
-import { QualityControlPage } from "@/pages/QualityControlPage";
-import { SystemHealthPage } from "@/pages/SystemHealthPage";
-import { SettingsPage } from "@/pages/SettingsPage";
+import { Skeleton } from "@/components/ui/skeleton";
+
+// Each page is split into its own chunk so the initial bundle only ships the
+// app shell + the page the user actually opens. Named exports are remapped to
+// the `default` slot React.lazy expects.
+const DashboardPage = lazy(() =>
+ import("@/pages/DashboardPage").then((m) => ({ default: m.DashboardPage }))
+);
+const DocumentsPage = lazy(() =>
+ import("@/pages/DocumentsPage").then((m) => ({ default: m.DocumentsPage }))
+);
+const IngestionJobsPage = lazy(() =>
+ import("@/pages/IngestionJobsPage").then((m) => ({ default: m.IngestionJobsPage }))
+);
+const SearchPage = lazy(() =>
+ import("@/pages/SearchPage").then((m) => ({ default: m.SearchPage }))
+);
+const DocumentViewerPage = lazy(() =>
+ import("@/pages/DocumentViewerPage").then((m) => ({ default: m.DocumentViewerPage }))
+);
+const TablesFiguresPage = lazy(() =>
+ import("@/pages/TablesFiguresPage").then((m) => ({ default: m.TablesFiguresPage }))
+);
+const QualityControlPage = lazy(() =>
+ import("@/pages/QualityControlPage").then((m) => ({ default: m.QualityControlPage }))
+);
+const SystemHealthPage = lazy(() =>
+ import("@/pages/SystemHealthPage").then((m) => ({ default: m.SystemHealthPage }))
+);
+const SettingsPage = lazy(() =>
+ import("@/pages/SettingsPage").then((m) => ({ default: m.SettingsPage }))
+);
+
+function RouteFallback() {
+ return (
+
+
+
+
+ {Array.from({ length: 4 }).map((_, i) => (
+
+ ))}
+
+
+
+ );
+}
+
+function withSuspense(node: React.ReactNode) {
+ return }>{node};
+}
export const router = createBrowserRouter([
{
element: ,
children: [
- { path: "/", element: },
- { path: "/documents", element: },
- { path: "/ingestion", element: },
- { path: "/search", element: },
- { path: "/viewer", element: },
- { path: "/viewer/:id", element: },
- { path: "/tables-figures", element: },
- { path: "/quality", element: },
- { path: "/health", element: },
- { path: "/settings", element: },
+ { path: "/", element: withSuspense() },
+ { path: "/documents", element: withSuspense() },
+ { path: "/ingestion", element: withSuspense() },
+ { path: "/search", element: withSuspense() },
+ { path: "/viewer", element: withSuspense() },
+ { path: "/viewer/:id", element: withSuspense() },
+ { path: "/tables-figures", element: withSuspense() },
+ { path: "/quality", element: withSuspense() },
+ { path: "/health", element: withSuspense() },
+ { path: "/settings", element: withSuspense() },
{ path: "*", element: },
],
},
diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts
index 5f0c73b..0c53312 100644
--- a/frontend/vite.config.ts
+++ b/frontend/vite.config.ts
@@ -22,5 +22,32 @@ export default defineConfig({
build: {
outDir: "dist",
sourcemap: true,
+ chunkSizeWarningLimit: 800,
+ rollupOptions: {
+ output: {
+ // Split heavy third-party libs into their own long-lived chunks so
+ // page-level chunks stay small and the vendor cache is reused across
+ // releases.
+ manualChunks: (id) => {
+ if (!id.includes("node_modules")) return undefined;
+ if (id.includes("recharts") || id.includes("d3-")) return "vendor-recharts";
+ if (id.includes("framer-motion")) return "vendor-motion";
+ if (id.includes("@radix-ui") || id.includes("cmdk")) return "vendor-radix";
+ if (id.includes("@tanstack")) return "vendor-tanstack";
+ if (id.includes("react-router")) return "vendor-router";
+ if (id.includes("axios")) return "vendor-axios";
+ if (id.includes("lucide-react")) return "vendor-lucide";
+ if (id.includes("zustand")) return "vendor-state";
+ if (id.includes("sonner")) return "vendor-toast";
+ if (
+ id.includes("react/") ||
+ id.includes("react-dom") ||
+ id.includes("scheduler")
+ )
+ return "vendor-react";
+ return "vendor";
+ },
+ },
+ },
},
});