From 461dad0dd5613d37ed5545d6a00e1d6cdaceb04a Mon Sep 17 00:00:00 2001 From: Bohan Jiang <52446949+Bohan-J@users.noreply.github.com> Date: Tue, 31 Mar 2026 15:22:58 +0800 Subject: [PATCH] perf(web): parallelize auth init and non-blocking dashboard layout (#220) - Fire getMe() and listWorkspaces() in parallel instead of serially, saving one network round-trip (~200ms on cloud) - Render dashboard sidebar shell immediately once user is authenticated, show loading indicator in content area while workspace hydrates Closes MUL-41 --- apps/web/app/(dashboard)/layout.tsx | 12 ++++++-- apps/web/features/auth/initializer.tsx | 39 +++++++++++++++++--------- 2 files changed, 35 insertions(+), 16 deletions(-) diff --git a/apps/web/app/(dashboard)/layout.tsx b/apps/web/app/(dashboard)/layout.tsx index 52e6f8b6..e9e78d16 100644 --- a/apps/web/app/(dashboard)/layout.tsx +++ b/apps/web/app/(dashboard)/layout.tsx @@ -38,12 +38,20 @@ export default function DashboardLayout({ ); } - if (!user || !workspace) return null; + if (!user) return null; return ( - {children} + + {workspace ? ( + children + ) : ( +
+ +
+ )} +
); } diff --git a/apps/web/features/auth/initializer.tsx b/apps/web/features/auth/initializer.tsx index ae7820c3..ffb0b87a 100644 --- a/apps/web/features/auth/initializer.tsx +++ b/apps/web/features/auth/initializer.tsx @@ -10,26 +10,37 @@ const logger = createLogger("auth"); /** * Initializes auth + workspace state from localStorage on mount. - * Must wrap the app to ensure stores are hydrated before children render. + * Fires getMe() and listWorkspaces() in parallel when a cached token exists. */ export function AuthInitializer({ children }: { children: ReactNode }) { - const initialize = useAuthStore((s) => s.initialize); - const user = useAuthStore((s) => s.user); - const isLoading = useAuthStore((s) => s.isLoading); - const hydrateWorkspace = useWorkspaceStore((s) => s.hydrateWorkspace); - useEffect(() => { - initialize(); - }, [initialize]); + const token = localStorage.getItem("multica_token"); + if (!token) { + useAuthStore.setState({ isLoading: false }); + return; + } - useEffect(() => { - if (isLoading || !user) return; + api.setToken(token); const wsId = localStorage.getItem("multica_workspace_id"); - api.listWorkspaces().then((wsList) => { - hydrateWorkspace(wsList, wsId); - }).catch((err) => logger.error("workspace hydration failed", err)); - }, [user, isLoading, hydrateWorkspace]); + // Fire getMe and listWorkspaces in parallel + const mePromise = api.getMe(); + const wsPromise = api.listWorkspaces(); + + Promise.all([mePromise, wsPromise]) + .then(([user, wsList]) => { + useAuthStore.setState({ user, isLoading: false }); + useWorkspaceStore.getState().hydrateWorkspace(wsList, wsId); + }) + .catch((err) => { + logger.error("auth init failed", err); + api.setToken(null); + api.setWorkspaceId(null); + localStorage.removeItem("multica_token"); + localStorage.removeItem("multica_workspace_id"); + useAuthStore.setState({ user: null, isLoading: false }); + }); + }, []); return <>{children}; }