diff --git a/src/App.tsx b/src/App.tsx
index 57fc917..1c815e6 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -4,14 +4,18 @@
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/
+import { Suspense } from 'react';
import { Outlet } from 'react-router-dom';
import { ErrorBoundary } from '@/components/layout/ErrorBoundary';
+import { LoadingFallback } from '@/components/common/LoadingFallback';
import { Toaster } from '@/components/ui/toaster';
export default function App() {
return (
-
+ }>
+
+
);
diff --git a/src/components/common/LoadingFallback.tsx b/src/components/common/LoadingFallback.tsx
new file mode 100644
index 0000000..e7b107e
--- /dev/null
+++ b/src/components/common/LoadingFallback.tsx
@@ -0,0 +1,24 @@
+/*
+ * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC
+ *
+ * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
+ */
+
+import { Loader2 } from 'lucide-react';
+import { useTranslation } from 'react-i18next';
+
+interface LoadingFallbackProps {
+ fullScreen?: boolean;
+}
+
+export function LoadingFallback({ fullScreen }: LoadingFallbackProps) {
+ const { t } = useTranslation();
+ return (
+
+
+
+
{t('common.loading')}
+
+
+ );
+}
diff --git a/src/components/layout/MainContent.tsx b/src/components/layout/MainContent.tsx
index 4b04c83..7706f2d 100644
--- a/src/components/layout/MainContent.tsx
+++ b/src/components/layout/MainContent.tsx
@@ -4,7 +4,7 @@
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/
-import { useEffect } from 'react';
+import { lazy, Suspense, useEffect } from 'react';
import { useSchemaStore } from '@/stores/schemaStore';
import { useCacheStore } from '@/stores/cacheStore';
import { useAccountStore } from '@/stores/accountStore';
@@ -12,11 +12,23 @@ import { resolveObject } from '@/lib/schemaResolver';
import { DynamicList } from '@/components/lists/DynamicList';
import { DynamicForm } from '@/components/forms/DynamicForm';
import { DynamicViewPage } from '@/components/views/DynamicViewPage';
-import { DashboardView } from '@/features/dashboard/components/DashboardView';
-import { DeliveryTracePage } from '@/features/troubleshoot/DeliveryTracePage';
-import { LiveTracingPage } from '@/features/tracing/components/LiveTracingPage';
-import { TraceDetailView } from '@/features/tracing/components/TraceDetailView';
-import { ActionPage } from '@/features/actions/ActionPage';
+import { LoadingFallback } from '@/components/common/LoadingFallback';
+
+// Heavy or rarely used feature pages are code-split so the initial bundle
+// stays small (the dashboard pulls in recharts, ~150 kB gzipped on its own).
+const DashboardView = lazy(() =>
+ import('@/features/dashboard/components/DashboardView').then((m) => ({ default: m.DashboardView })),
+);
+const DeliveryTracePage = lazy(() =>
+ import('@/features/troubleshoot/DeliveryTracePage').then((m) => ({ default: m.DeliveryTracePage })),
+);
+const LiveTracingPage = lazy(() =>
+ import('@/features/tracing/components/LiveTracingPage').then((m) => ({ default: m.LiveTracingPage })),
+);
+const TraceDetailView = lazy(() =>
+ import('@/features/tracing/components/TraceDetailView').then((m) => ({ default: m.TraceDetailView })),
+);
+const ActionPage = lazy(() => import('@/features/actions/ActionPage').then((m) => ({ default: m.ActionPage })));
interface MainContentProps {
viewName?: string;
@@ -25,6 +37,14 @@ interface MainContentProps {
}
export function MainContent({ viewName, id, section }: MainContentProps) {
+ return (
+ }>
+
+
+ );
+}
+
+function MainContentView({ viewName, id, section }: MainContentProps) {
const schema = useSchemaStore((s) => s.schema);
const invalidateAllObjectLists = useCacheStore((s) => s.invalidateAllObjectLists);
diff --git a/src/main.tsx b/src/main.tsx
index 5cfa5ce..06656fa 100644
--- a/src/main.tsx
+++ b/src/main.tsx
@@ -12,8 +12,8 @@ import './index.css';
import App from './App';
import LoginPage from './pages/LoginPage';
import OAuthCallback from './pages/OAuthCallback';
-import AdminPanel from './pages/AdminPanel';
import NotFound from './pages/NotFound';
+import { AdminPanel } from './pages/AdminPanel.lazy';
import { ProtectedRoute } from './components/layout/ProtectedRoute';
import { getBasePath } from './lib/basePath';
diff --git a/src/pages/AdminPanel.lazy.tsx b/src/pages/AdminPanel.lazy.tsx
new file mode 100644
index 0000000..03a6a3d
--- /dev/null
+++ b/src/pages/AdminPanel.lazy.tsx
@@ -0,0 +1,12 @@
+/*
+ * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC
+ *
+ * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
+ */
+
+import { lazy } from 'react';
+
+// The admin panel (and everything reachable from it) is split out of the
+// entry chunk so anonymous visitors only download the login page.
+// Kept in a dedicated file so main.tsx stays component-free for fast refresh.
+export const AdminPanel = lazy(() => import('./AdminPanel'));
diff --git a/src/pages/AdminPanel.tsx b/src/pages/AdminPanel.tsx
index 76e80ac..7959abd 100644
--- a/src/pages/AdminPanel.tsx
+++ b/src/pages/AdminPanel.tsx
@@ -4,7 +4,7 @@
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/
-import { useEffect, useMemo, useState } from 'react';
+import { lazy, Suspense, useEffect, useMemo, useState } from 'react';
import { useParams, useNavigate } from 'react-router-dom';
import { useTranslation } from 'react-i18next';
import { useAuthStore } from '@/stores/authStore';
@@ -17,7 +17,7 @@ import { TopBar } from '@/components/layout/TopBar';
import { Sidebar } from '@/components/layout/Sidebar';
import { MainContent } from '@/components/layout/MainContent';
import { ErrorBoundary } from '@/components/layout/ErrorBoundary';
-import { BootstrapWizard } from '@/components/bootstrap/BootstrapWizard';
+import { LoadingFallback } from '@/components/common/LoadingFallback';
import {
findFirstAccessibleLinkInLayout,
findFirstVisibleLinkInLayout,
@@ -27,6 +27,11 @@ import {
import { usePermissions } from '@/hooks/usePermissions';
import { Loader2 } from 'lucide-react';
+// Bootstrap mode is a rare first-run flow; keep it out of the main chunk.
+const BootstrapWizard = lazy(() =>
+ import('@/components/bootstrap/BootstrapWizard').then((m) => ({ default: m.BootstrapWizard })),
+);
+
export default function AdminPanel() {
const { t } = useTranslation();
const navigate = useNavigate();
@@ -237,7 +242,9 @@ export default function AdminPanel() {
if (isBootstrapMode) {
return (
-
+ }>
+
+
);
}