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"; + }, + }, + }, }, });