diff --git a/web/apps/admin/src/App.tsx b/web/apps/admin/src/App.tsx
index 541a2be6a..4d2720e35 100644
--- a/web/apps/admin/src/App.tsx
+++ b/web/apps/admin/src/App.tsx
@@ -1,16 +1,22 @@
import { Flex } from "@raystack/apsara";
+import { Suspense } from "react";
import { Outlet } from "react-router-dom";
import "@raystack/apsara/normalize.css";
import "@raystack/apsara/style.css";
import "./App.css";
import IAMSidebar from "./components/Sidebar";
+import LoadingState from "./components/states/Loading";
function App() {
return (
-
+ {/* Boundary for the lazily-loaded route pages. Sits inside the layout
+ so the sidebar stays mounted while a route chunk loads. */}
+ }>
+
+
);
diff --git a/web/apps/admin/src/pages/admins/AdminsPage.tsx b/web/apps/admin/src/pages/admins/AdminsPage.tsx
index 7b8140ebf..af33228c6 100644
--- a/web/apps/admin/src/pages/admins/AdminsPage.tsx
+++ b/web/apps/admin/src/pages/admins/AdminsPage.tsx
@@ -2,7 +2,7 @@ import { AdminsView, useAdminPaths } from "@raystack/frontier/admin";
import { useNavigate } from "react-router-dom";
import AdminsIcon from "~/assets/icons/admins.svg?react";
-export function AdminsPage() {
+export default function AdminsPage() {
const navigate = useNavigate();
const paths = useAdminPaths();
diff --git a/web/apps/admin/src/pages/audit-logs/AuditLogsPage.tsx b/web/apps/admin/src/pages/audit-logs/AuditLogsPage.tsx
index 92214e7a4..a89e21266 100644
--- a/web/apps/admin/src/pages/audit-logs/AuditLogsPage.tsx
+++ b/web/apps/admin/src/pages/audit-logs/AuditLogsPage.tsx
@@ -7,7 +7,7 @@ import type { RQLExportRequest, RQLRequest } from "@raystack/proton/frontier";
const adminClient = clients.admin({ useBinary: true });
-export function AuditLogsPage() {
+export default function AuditLogsPage() {
const navigate = useNavigate();
const onExportCsv = useCallback(async (query: RQLRequest) => {
await exportCsvFromStream(
diff --git a/web/apps/admin/src/pages/invoices/InvoicesPage.tsx b/web/apps/admin/src/pages/invoices/InvoicesPage.tsx
index 32501e84c..8ecd39a98 100644
--- a/web/apps/admin/src/pages/invoices/InvoicesPage.tsx
+++ b/web/apps/admin/src/pages/invoices/InvoicesPage.tsx
@@ -1,5 +1,5 @@
import { InvoicesView } from "@raystack/frontier/admin";
-export function InvoicesPage() {
+export default function InvoicesPage() {
return ;
}
diff --git a/web/apps/admin/src/pages/organizations/details/index.tsx b/web/apps/admin/src/pages/organizations/details/index.tsx
index ce1578335..c7b84e8e7 100644
--- a/web/apps/admin/src/pages/organizations/details/index.tsx
+++ b/web/apps/admin/src/pages/organizations/details/index.tsx
@@ -12,7 +12,7 @@ async function loadCountries(): Promise {
return (data.default as { name: string }[]).map((c) => c.name);
}
-export function OrganizationDetailsPage() {
+export default function OrganizationDetailsPage() {
const { organizationId } = useParams<{ organizationId: string }>();
const location = useLocation();
const navigate = useNavigate();
diff --git a/web/apps/admin/src/pages/organizations/list/index.tsx b/web/apps/admin/src/pages/organizations/list/index.tsx
index 6d5e0079d..83e0dd26e 100644
--- a/web/apps/admin/src/pages/organizations/list/index.tsx
+++ b/web/apps/admin/src/pages/organizations/list/index.tsx
@@ -15,7 +15,7 @@ async function loadCountries(): Promise {
return (data.default as { name: string }[]).map((c) => c.name);
}
-export function OrganizationListPage() {
+export default function OrganizationListPage() {
const navigate = useNavigate();
const { config } = useContext(AppContext);
const paths = useAdminPaths();
diff --git a/web/apps/admin/src/pages/plans/PlansPage.tsx b/web/apps/admin/src/pages/plans/PlansPage.tsx
index ce78058b8..3a5bbef8d 100644
--- a/web/apps/admin/src/pages/plans/PlansPage.tsx
+++ b/web/apps/admin/src/pages/plans/PlansPage.tsx
@@ -2,7 +2,7 @@ import { PlansView } from "@raystack/frontier/admin";
import { useNavigate, useParams } from "react-router-dom";
import PlansIcon from "~/assets/icons/plans.svg?react";
-export function PlansPage() {
+export default function PlansPage() {
const { planId } = useParams();
const navigate = useNavigate();
diff --git a/web/apps/admin/src/pages/preferences/PreferencesPage.tsx b/web/apps/admin/src/pages/preferences/PreferencesPage.tsx
index 43c716895..2619359c6 100644
--- a/web/apps/admin/src/pages/preferences/PreferencesPage.tsx
+++ b/web/apps/admin/src/pages/preferences/PreferencesPage.tsx
@@ -2,7 +2,7 @@ import { useParams, useNavigate } from "react-router-dom";
import { PreferencesView } from "@raystack/frontier/admin";
import PreferencesIcon from "~/assets/icons/preferences.svg?react";
-export function PreferencesPage() {
+export default function PreferencesPage() {
const { name } = useParams();
const navigate = useNavigate();
diff --git a/web/apps/admin/src/pages/products/ProductPricesPage.tsx b/web/apps/admin/src/pages/products/ProductPricesPage.tsx
index ea84284c6..a2b8e2f23 100644
--- a/web/apps/admin/src/pages/products/ProductPricesPage.tsx
+++ b/web/apps/admin/src/pages/products/ProductPricesPage.tsx
@@ -1,7 +1,7 @@
import { ProductPricesView } from "@raystack/frontier/admin";
import { useParams, useNavigate } from "react-router-dom";
-export function ProductPricesPage() {
+export default function ProductPricesPage() {
const { productId } = useParams();
const navigate = useNavigate();
diff --git a/web/apps/admin/src/pages/products/ProductsPage.tsx b/web/apps/admin/src/pages/products/ProductsPage.tsx
index a36dd0bab..460b2abda 100644
--- a/web/apps/admin/src/pages/products/ProductsPage.tsx
+++ b/web/apps/admin/src/pages/products/ProductsPage.tsx
@@ -2,7 +2,7 @@ import { ProductsView } from "@raystack/frontier/admin";
import { useParams, useNavigate } from "react-router-dom";
import ProductsIcon from "~/assets/icons/products.svg?react";
-export function ProductsPage() {
+export default function ProductsPage() {
const { productId } = useParams();
const navigate = useNavigate();
diff --git a/web/apps/admin/src/pages/roles/RolesPage.tsx b/web/apps/admin/src/pages/roles/RolesPage.tsx
index ae4b8248d..0a06079c9 100644
--- a/web/apps/admin/src/pages/roles/RolesPage.tsx
+++ b/web/apps/admin/src/pages/roles/RolesPage.tsx
@@ -2,7 +2,7 @@ import { RolesView } from "@raystack/frontier/admin";
import { useParams, useNavigate } from "react-router-dom";
import RolesIcon from "~/assets/icons/roles.svg?react";
-export function RolesPage() {
+export default function RolesPage() {
const { roleId } = useParams();
const navigate = useNavigate();
diff --git a/web/apps/admin/src/pages/users/UsersPage.tsx b/web/apps/admin/src/pages/users/UsersPage.tsx
index b82a684fe..7a105cd95 100644
--- a/web/apps/admin/src/pages/users/UsersPage.tsx
+++ b/web/apps/admin/src/pages/users/UsersPage.tsx
@@ -6,7 +6,7 @@ import { exportCsvFromStream } from "~/utils/helper";
const adminClient = clients.admin({ useBinary: true });
-export function UsersPage() {
+export default function UsersPage() {
const { userId } = useParams();
const navigate = useNavigate();
const location = useLocation();
diff --git a/web/apps/admin/src/pages/webhooks/WebhooksPage.tsx b/web/apps/admin/src/pages/webhooks/WebhooksPage.tsx
index 510d6c0ab..b919da73b 100644
--- a/web/apps/admin/src/pages/webhooks/WebhooksPage.tsx
+++ b/web/apps/admin/src/pages/webhooks/WebhooksPage.tsx
@@ -4,7 +4,7 @@ import { WebhooksView } from "@raystack/frontier/admin";
import { AppContext } from "~/contexts/App";
import WebhooksIcon from "~/assets/icons/webhooks.svg?react";
-export function WebhooksPage() {
+export default function WebhooksPage() {
const { config } = useContext(AppContext);
const { webhookId } = useParams();
const navigate = useNavigate();
diff --git a/web/apps/admin/src/routes.tsx b/web/apps/admin/src/routes.tsx
index e4fd66bb8..1cf1327ff 100644
--- a/web/apps/admin/src/routes.tsx
+++ b/web/apps/admin/src/routes.tsx
@@ -1,43 +1,75 @@
import * as R from "ramda";
-import { memo, useContext } from "react";
+import { lazy, memo, useContext } from "react";
import { Navigate, Route, Routes } from "react-router-dom";
import LoadingState from "./components/states/Loading";
import UnauthorizedState from "./components/states/Unauthorized";
+// Eager: the app shell and the unauthenticated flow. Keeping these in the
+// initial bundle means the login screen paints instantly.
import App from "./App";
-import { PlansPage } from "./pages/plans/PlansPage";
import Login from "./containers/login";
import MagicLink from "./containers/magiclink";
-
-import { PreferencesPage } from "./pages/preferences/PreferencesPage";
-import { ProductsPage } from "./pages/products/ProductsPage";
-import { ProductPricesPage } from "./pages/products/ProductPricesPage";
-
-import { RolesPage } from "./pages/roles/RolesPage";
-
-import { AppContext } from "./contexts/App";
-import { AdminsPage } from "./pages/admins/AdminsPage";
-import { WebhooksPage } from "./pages/webhooks/WebhooksPage";
import AuthLayout from "./layout/auth";
-import { OrganizationListPage } from "./pages/organizations/list";
-import { OrganizationDetailsPage } from "./pages/organizations/details";
-import {
- OrganizationSecurity,
- OrganizationMembersView,
- OrganizationProjectsView,
- OrganizationInvoicesView,
- OrganizationTokensView,
- OrganizationApisView,
- OrganizationPatView,
- useAdminPaths,
-} from "@raystack/frontier/admin";
-
-import { UsersPage } from "./pages/users/UsersPage";
-
-import { InvoicesPage } from "./pages/invoices/InvoicesPage";
-import { AuditLogsPage } from "./pages/audit-logs/AuditLogsPage";
+import { AppContext } from "./contexts/App";
+// useAdminPaths is a hook called during render, so it must stay a static import.
+import { useAdminPaths } from "@raystack/frontier/admin";
+
+// Lazily-loaded route pages — each becomes its own async chunk, so the heavy
+// admin code stays out of the initial/unauthenticated bundle. The pages are
+// default exports, so React.lazy imports them directly (per the React docs).
+const PlansPage = lazy(() => import("./pages/plans/PlansPage"));
+const PreferencesPage = lazy(() => import("./pages/preferences/PreferencesPage"));
+const ProductsPage = lazy(() => import("./pages/products/ProductsPage"));
+const ProductPricesPage = lazy(() => import("./pages/products/ProductPricesPage"));
+const RolesPage = lazy(() => import("./pages/roles/RolesPage"));
+const AdminsPage = lazy(() => import("./pages/admins/AdminsPage"));
+const WebhooksPage = lazy(() => import("./pages/webhooks/WebhooksPage"));
+const OrganizationListPage = lazy(() => import("./pages/organizations/list"));
+const OrganizationDetailsPage = lazy(() => import("./pages/organizations/details"));
+const UsersPage = lazy(() => import("./pages/users/UsersPage"));
+const InvoicesPage = lazy(() => import("./pages/invoices/InvoicesPage"));
+const AuditLogsPage = lazy(() => import("./pages/audit-logs/AuditLogsPage"));
+
+// Organization detail child views come from the SDK barrel. Each lazy() shares
+// the same `@raystack/frontier/admin` chunk (the module promise is cached), so
+// the admin SDK loads once on the first org route and stays cached after.
+const OrganizationSecurity = lazy(() =>
+ import("@raystack/frontier/admin").then((m) => ({
+ default: m.OrganizationSecurity,
+ })),
+);
+const OrganizationMembersView = lazy(() =>
+ import("@raystack/frontier/admin").then((m) => ({
+ default: m.OrganizationMembersView,
+ })),
+);
+const OrganizationProjectsView = lazy(() =>
+ import("@raystack/frontier/admin").then((m) => ({
+ default: m.OrganizationProjectsView,
+ })),
+);
+const OrganizationInvoicesView = lazy(() =>
+ import("@raystack/frontier/admin").then((m) => ({
+ default: m.OrganizationInvoicesView,
+ })),
+);
+const OrganizationTokensView = lazy(() =>
+ import("@raystack/frontier/admin").then((m) => ({
+ default: m.OrganizationTokensView,
+ })),
+);
+const OrganizationApisView = lazy(() =>
+ import("@raystack/frontier/admin").then((m) => ({
+ default: m.OrganizationApisView,
+ })),
+);
+const OrganizationPatView = lazy(() =>
+ import("@raystack/frontier/admin").then((m) => ({
+ default: m.OrganizationPatView,
+ })),
+);
export default memo(function AppRoutes() {
const { isAdmin, isLoading, user } = useContext(AppContext);