Lazy loading for smaller build chunks/faster initial load

This commit is contained in:
2025-06-22 21:07:17 -04:00
parent 8496bbc4ee
commit 520ff5bd74
4 changed files with 124 additions and 36 deletions

View File

@@ -1,27 +1,41 @@
import { Routes, Route, useNavigate, Navigate, useLocation } from 'react-router-dom'; import { Routes, Route, useNavigate, Navigate, useLocation } from 'react-router-dom';
import { MainLayout } from './components/layout/MainLayout'; import { MainLayout } from './components/layout/MainLayout';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { Products } from './pages/Products';
import { Overview } from './pages/Overview';
import { Settings } from './pages/Settings';
import { Analytics } from './pages/Analytics';
import { Toaster } from '@/components/ui/sonner'; import { Toaster } from '@/components/ui/sonner';
import PurchaseOrders from './pages/PurchaseOrders'; import { useEffect, Suspense, lazy } from 'react';
import { Login } from './pages/Login';
import { useEffect } from 'react';
import config from './config'; import config from './config';
import { RequireAuth } from './components/auth/RequireAuth'; import { RequireAuth } from './components/auth/RequireAuth';
import Forecasting from "@/pages/Forecasting";
import { Vendors } from '@/pages/Vendors';
import { Categories } from '@/pages/Categories';
import { Import } from '@/pages/Import';
import { AuthProvider } from './contexts/AuthContext'; import { AuthProvider } from './contexts/AuthContext';
import { Protected } from './components/auth/Protected'; import { Protected } from './components/auth/Protected';
import { FirstAccessiblePage } from './components/auth/FirstAccessiblePage'; import { FirstAccessiblePage } from './components/auth/FirstAccessiblePage';
import { Brands } from '@/pages/Brands'; import { PageLoading } from '@/components/ui/page-loading';
import { Chat } from '@/pages/Chat';
import { Dashboard } from '@/pages/Dashboard'; // Always loaded components (Login and Settings stay in main bundle)
import { SmallDashboard } from '@/pages/SmallDashboard'; import { Settings } from './pages/Settings';
import { Login } from './pages/Login';
// Lazy load the 4 main chunks you wanted:
// 1. Core inventory app - loaded as one chunk when any inventory page is accessed
const Overview = lazy(() => import('./pages/Overview'));
const Products = lazy(() => import('./pages/Products').then(module => ({ default: module.Products })));
const Analytics = lazy(() => import('./pages/Analytics').then(module => ({ default: module.Analytics })));
const Forecasting = lazy(() => import('./pages/Forecasting'));
const Vendors = lazy(() => import('./pages/Vendors'));
const Categories = lazy(() => import('./pages/Categories'));
const Brands = lazy(() => import('./pages/Brands'));
const PurchaseOrders = lazy(() => import('./pages/PurchaseOrders'));
// 2. Dashboard app - separate chunk
const Dashboard = lazy(() => import('./pages/Dashboard'));
const SmallDashboard = lazy(() => import('./pages/SmallDashboard'));
// 3. Product import - separate chunk
const Import = lazy(() => import('./pages/Import').then(module => ({ default: module.Import })));
// 4. Chat archive - separate chunk
const Chat = lazy(() => import('./pages/Chat').then(module => ({ default: module.Chat })));
const queryClient = new QueryClient(); const queryClient = new QueryClient();
function App() { function App() {
@@ -75,78 +89,117 @@ function App() {
<AuthProvider> <AuthProvider>
<Toaster richColors position="top-center" /> <Toaster richColors position="top-center" />
<Routes> <Routes>
{/* Always loaded routes */}
<Route path="/login" element={<Login />} /> <Route path="/login" element={<Login />} />
<Route path="/small" element={<SmallDashboard />} /> <Route path="/small" element={
<Suspense fallback={<PageLoading />}>
<SmallDashboard />
</Suspense>
} />
<Route element={ <Route element={
<RequireAuth> <RequireAuth>
<MainLayout /> <MainLayout />
</RequireAuth> </RequireAuth>
}> }>
{/* Core inventory app routes - will be lazy loaded */}
<Route index element={ <Route index element={
<Protected page="dashboard" fallback={<FirstAccessiblePage />}> <Protected page="dashboard" fallback={<FirstAccessiblePage />}>
<Suspense fallback={<PageLoading />}>
<Overview /> <Overview />
</Suspense>
</Protected> </Protected>
} /> } />
<Route path="/" element={ <Route path="/" element={
<Protected page="dashboard"> <Protected page="dashboard">
<Suspense fallback={<PageLoading />}>
<Overview /> <Overview />
</Suspense>
</Protected> </Protected>
} /> } />
<Route path="/products" element={ <Route path="/products" element={
<Protected page="products"> <Protected page="products">
<Suspense fallback={<PageLoading />}>
<Products /> <Products />
</Protected> </Suspense>
} />
<Route path="/import" element={
<Protected page="import">
<Import />
</Protected> </Protected>
} /> } />
<Route path="/categories" element={ <Route path="/categories" element={
<Protected page="categories"> <Protected page="categories">
<Suspense fallback={<PageLoading />}>
<Categories /> <Categories />
</Suspense>
</Protected> </Protected>
} /> } />
<Route path="/vendors" element={ <Route path="/vendors" element={
<Protected page="vendors"> <Protected page="vendors">
<Suspense fallback={<PageLoading />}>
<Vendors /> <Vendors />
</Suspense>
</Protected> </Protected>
} /> } />
<Route path="/brands" element={ <Route path="/brands" element={
<Protected page="brands"> <Protected page="brands">
<Suspense fallback={<PageLoading />}>
<Brands /> <Brands />
</Suspense>
</Protected> </Protected>
} /> } />
<Route path="/purchase-orders" element={ <Route path="/purchase-orders" element={
<Protected page="purchase_orders"> <Protected page="purchase_orders">
<Suspense fallback={<PageLoading />}>
<PurchaseOrders /> <PurchaseOrders />
</Suspense>
</Protected> </Protected>
} /> } />
<Route path="/analytics" element={ <Route path="/analytics" element={
<Protected page="analytics"> <Protected page="analytics">
<Suspense fallback={<PageLoading />}>
<Analytics /> <Analytics />
</Suspense>
</Protected> </Protected>
} /> } />
<Route path="/forecasting" element={
<Protected page="forecasting">
<Suspense fallback={<PageLoading />}>
<Forecasting />
</Suspense>
</Protected>
} />
{/* Always loaded settings */}
<Route path="/settings" element={ <Route path="/settings" element={
<Protected page="settings"> <Protected page="settings">
<Settings /> <Settings />
</Protected> </Protected>
} /> } />
<Route path="/forecasting" element={
<Protected page="forecasting"> {/* Product import - separate chunk */}
<Forecasting /> <Route path="/import" element={
<Protected page="import">
<Suspense fallback={<PageLoading />}>
<Import />
</Suspense>
</Protected> </Protected>
} /> } />
{/* Chat archive - separate chunk */}
<Route path="/chat" element={ <Route path="/chat" element={
<Protected page="chat"> <Protected page="chat">
<Suspense fallback={<PageLoading />}>
<Chat /> <Chat />
</Suspense>
</Protected> </Protected>
} /> } />
{/* Dashboard app - separate chunk */}
<Route path="/dashboard" element={ <Route path="/dashboard" element={
<Protected page="dashboard"> <Protected page="dashboard">
<Suspense fallback={<PageLoading />}>
<Dashboard /> <Dashboard />
</Suspense>
</Protected> </Protected>
} /> } />
<Route path="*" element={<Navigate to="/" replace />} /> <Route path="*" element={<Navigate to="/" replace />} />
</Route> </Route>
</Routes> </Routes>

View File

@@ -0,0 +1,31 @@
import { Loader2 } from 'lucide-react';
interface PageLoadingProps {
message?: string;
size?: 'sm' | 'md' | 'lg';
}
export const PageLoading = ({ message = 'Loading...', size = 'md' }: PageLoadingProps) => {
const sizeClasses = {
sm: 'h-6 w-6',
md: 'h-8 w-8',
lg: 'h-12 w-12'
};
const containerClasses = {
sm: 'min-h-[200px]',
md: 'min-h-[400px]',
lg: 'min-h-[600px]'
};
return (
<div className={`flex items-center justify-center ${containerClasses[size]}`}>
<div className="flex flex-col items-center space-y-4">
<Loader2 className={`animate-spin text-primary ${sizeClasses[size]}`} />
<p className="text-sm text-muted-foreground animate-pulse">{message}</p>
</div>
</div>
);
};
export default PageLoading;

File diff suppressed because one or more lines are too long

View File

@@ -221,10 +221,14 @@ export default defineConfig(({ mode }) => {
build: { build: {
outDir: "build", outDir: "build",
sourcemap: true, sourcemap: true,
chunkSizeWarningLimit: 1000,
rollupOptions: { rollupOptions: {
output: { output: {
manualChunks: { manualChunks: {
vendor: ["react", "react-dom", "react-router-dom"], // Simple static chunking approach - safer than function-based chunking
'react-vendor': ['react', 'react-dom', 'react-router-dom'],
'ui-vendor': ['@radix-ui/react-dialog', '@radix-ui/react-dropdown-menu', 'lucide-react'],
'query-vendor': ['@tanstack/react-query'],
}, },
}, },
}, },