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
This commit is contained in:
Bohan Jiang 2026-03-31 15:22:58 +08:00 committed by GitHub
parent 56b66908a1
commit 461dad0dd5
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 35 additions and 16 deletions

View file

@ -38,12 +38,20 @@ export default function DashboardLayout({
);
}
if (!user || !workspace) return null;
if (!user) return null;
return (
<SidebarProvider className="h-svh">
<AppSidebar />
<SidebarInset className="overflow-hidden">{children}</SidebarInset>
<SidebarInset className="overflow-hidden">
{workspace ? (
children
) : (
<div className="flex flex-1 items-center justify-center">
<MulticaIcon className="size-6 animate-pulse" />
</div>
)}
</SidebarInset>
</SidebarProvider>
);
}

View file

@ -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}</>;
}