From a8be1d2d1de80c2b433e1910e9a0b945b2e582cc Mon Sep 17 00:00:00 2001 From: ved015 Date: Wed, 10 Jun 2026 14:10:29 +0530 Subject: [PATCH] perf(web): defer non-critical app load work --- apps/web/app/(app)/page.tsx | 15 ++++++- apps/web/components/dashboard-view.tsx | 30 +++++++++---- apps/web/components/header.tsx | 8 ++-- apps/web/hooks/use-container-tags.ts | 11 ++++- apps/web/hooks/use-org-summaries.ts | 17 +++++++- apps/web/hooks/use-personalization.ts | 10 ++++- packages/lib/auth-context.tsx | 30 ++++++++++--- packages/lib/posthog.tsx | 60 ++++++++++++++++++++------ 8 files changed, 145 insertions(+), 36 deletions(-) diff --git a/apps/web/app/(app)/page.tsx b/apps/web/app/(app)/page.tsx index 98febe44..fbb4952c 100644 --- a/apps/web/app/(app)/page.tsx +++ b/apps/web/app/(app)/page.tsx @@ -131,6 +131,18 @@ export default function NewPage() { const { viewMode, setViewMode } = useViewMode() const queryClient = useQueryClient() const [highlightsForceAt, setHighlightsForceAt] = useState(0) + const [loadHomeAsyncData, setLoadHomeAsyncData] = useState(false) + + useEffect(() => { + if (!user) { + setLoadHomeAsyncData(false) + return + } + + setLoadHomeAsyncData(false) + const timeout = window.setTimeout(() => setLoadHomeAsyncData(true), 900) + return () => window.clearTimeout(timeout) + }, [user]) // Chrome extension auth: send session token via postMessage so the content script can store it useEffect(() => { @@ -363,6 +375,7 @@ export default function NewPage() { }, staleTime: HIGHLIGHTS_MAX_AGE, refetchOnWindowFocus: false, + enabled: loadHomeAsyncData || highlightsForceAt > 0, }) const { data: memoryOfDay = null } = useQuery({ @@ -393,7 +406,7 @@ export default function NewPage() { }, staleTime: 24 * 60 * 60 * 1000, refetchOnWindowFocus: false, - enabled: !!user, + enabled: loadHomeAsyncData && !!user, }) useHotkeys("c", () => { diff --git a/apps/web/components/dashboard-view.tsx b/apps/web/components/dashboard-view.tsx index fe2e3f0c..70e5d76e 100644 --- a/apps/web/components/dashboard-view.tsx +++ b/apps/web/components/dashboard-view.tsx @@ -1178,6 +1178,19 @@ export function DashboardView({ const { user, org } = useAuth() const { effectiveContainerTags } = useProject() const _router = useRouter() + const [loadSupportData, setLoadSupportData] = useState(false) + + useEffect(() => { + if (!user) { + setLoadSupportData(false) + return + } + + setLoadSupportData(false) + const timeout = window.setTimeout(() => setLoadSupportData(true), 900) + return () => window.clearTimeout(timeout) + }, [user]) + const { data: recentsData, isPending: isRecentsLoading } = useQuery({ queryKey: ["dashboard-recents", effectiveContainerTags], queryFn: async (): Promise => { @@ -1195,7 +1208,7 @@ export function DashboardView({ return response.data as DocumentsResponse }, staleTime: 60 * 1000, - enabled: !!user && !!org?.id, + enabled: loadSupportData && !!user && !!org?.id, }) const { data: connections = [] } = useQuery({ @@ -1208,7 +1221,7 @@ export function DashboardView({ return response.data ?? [] }, staleTime: 5 * 60 * 1000, - enabled: !!user, + enabled: loadSupportData && !!user, }) const { data: mcpData } = useQuery({ @@ -1218,7 +1231,7 @@ export function DashboardView({ return response.data ?? { previousLogin: false } }, staleTime: 5 * 60 * 1000, - enabled: !!user, + enabled: loadSupportData && !!user, }) // Fetch API keys for tool usage tracking @@ -1242,7 +1255,7 @@ export function DashboardView({ } }, staleTime: 5 * 60 * 1000, - enabled: !!user, + enabled: loadSupportData && !!user, }) const { data: recentMcpDocumentsData } = useQuery({ @@ -1261,7 +1274,7 @@ export function DashboardView({ return response.data as DocumentsResponse }, staleTime: 5 * 60 * 1000, - enabled: !!user, + enabled: loadSupportData && !!user, }) const toolUsageItems = useMemo( @@ -1277,7 +1290,7 @@ export function DashboardView({ copy: personalizedCopy, profession, setProfession, - } = usePersonalization() + } = usePersonalization({ enabled: loadSupportData }) const recents = recentsData?.documents ?? [] const recentToolUsageItems = toolUsageItems @@ -1288,6 +1301,7 @@ export function DashboardView({ return bTime - aTime }) .slice(0, 3) + const showRecentsLoading = !loadSupportData || isRecentsLoading const totalMemories = recentsData?.pagination?.totalItems ?? 0 const hasMcp = mcpData?.previousLogin ?? false const connectedProviders = new Set(connections.map((c) => c.provider)) @@ -1493,7 +1507,7 @@ export function DashboardView({
- {isRecentsLoading ? ( + {showRecentsLoading ? (
- {(isRecentsLoading || recents.length === 0) && ( + {(showRecentsLoading || recents.length === 0) && (

Suggested for you diff --git a/apps/web/components/header.tsx b/apps/web/components/header.tsx index e47f56c2..ca19a8dc 100644 --- a/apps/web/components/header.tsx +++ b/apps/web/components/header.tsx @@ -39,8 +39,7 @@ import { FeedbackModal } from "./feedback-modal" import { useViewMode } from "@/lib/view-mode-context" import { useQueryState } from "nuqs" import { feedbackParam } from "@/lib/search-params" -import { useCustomer } from "autumn-js/react" -import { useTokenUsage } from "@/hooks/use-token-usage" +import type { PlanType } from "@/hooks/use-token-usage" import { useOrgSummaries } from "@/hooks/use-org-summaries" import { OrgPlanBadge, resolveOrgPlan } from "@/components/org-plan-badge" import { useSettingsModal } from "@/components/settings/settings-modal" @@ -67,12 +66,13 @@ const brainTileClass = (active: boolean) => export function Header({ onAddMemory, onOpenSearch }: HeaderProps) { const { user, isRestoring, org, organizations, setActiveOrg } = useAuth() - const autumn = useCustomer() - const { currentPlan } = useTokenUsage(autumn) const { data: orgSummaries } = useOrgSummaries() const planByOrgId = new Map( (orgSummaries ?? []).map((s) => [s.orgId, s.plan] as const), ) + const currentPlan: PlanType = org?.id + ? (planByOrgId.get(org.id) ?? "free") + : "free" const { selectedProjects, setSelectedProjects } = useProject() const { openSettings } = useSettingsModal() const isMobile = useIsMobile() diff --git a/apps/web/hooks/use-container-tags.ts b/apps/web/hooks/use-container-tags.ts index b38c1906..7ba3c83f 100644 --- a/apps/web/hooks/use-container-tags.ts +++ b/apps/web/hooks/use-container-tags.ts @@ -1,10 +1,18 @@ "use client" import { useQuery } from "@tanstack/react-query" +import { useEffect, useState } from "react" import { $fetch } from "@lib/api" import type { ContainerTagListType } from "@lib/types" export function useContainerTags() { + const [enabled, setEnabled] = useState(false) + + useEffect(() => { + const timeout = window.setTimeout(() => setEnabled(true), 900) + return () => window.clearTimeout(timeout) + }, []) + const { data: allProjects = [], isLoading } = useQuery({ queryKey: ["container-tags"], queryFn: async () => { @@ -15,10 +23,11 @@ export function useContainerTags() { return (response.data || []) as ContainerTagListType[] }, staleTime: 30 * 1000, + enabled, }) return { allProjects, - isLoading, + isLoading: enabled && isLoading, } } diff --git a/apps/web/hooks/use-org-summaries.ts b/apps/web/hooks/use-org-summaries.ts index af2bb6ff..e66eab02 100644 --- a/apps/web/hooks/use-org-summaries.ts +++ b/apps/web/hooks/use-org-summaries.ts @@ -1,4 +1,7 @@ +"use client" + import { useQuery } from "@tanstack/react-query" +import { useEffect, useState } from "react" import { useAuth } from "@lib/auth-context" import { normalizePlanType, type PlanType } from "@/hooks/use-token-usage" @@ -15,6 +18,18 @@ export type OrgSummary = { export function useOrgSummaries() { const { user } = useAuth() + const [enabled, setEnabled] = useState(false) + + useEffect(() => { + if (!user?.id) { + setEnabled(false) + return + } + + setEnabled(false) + const timeout = window.setTimeout(() => setEnabled(true), 1200) + return () => window.clearTimeout(timeout) + }, [user?.id]) return useQuery({ queryKey: ["account", "org-summaries"], @@ -32,7 +47,7 @@ export function useOrgSummaries() { plan: normalizePlanType(s.plan), })) }, - enabled: !!user?.id, + enabled, staleTime: 60 * 1000, }) } diff --git a/apps/web/hooks/use-personalization.ts b/apps/web/hooks/use-personalization.ts index 619b3ac2..ee43e210 100644 --- a/apps/web/hooks/use-personalization.ts +++ b/apps/web/hooks/use-personalization.ts @@ -401,7 +401,11 @@ function classifyProfession(results: SearchResult[]): Profession { let inflightPromise: Promise | null = null -export function usePersonalization(): { +export function usePersonalization({ + enabled = true, +}: { + enabled?: boolean +} = {}): { copy: PersonalizedCopy profession: Profession setProfession: (p: Profession) => void @@ -441,6 +445,8 @@ export function usePersonalization(): { } } catch {} + if (!enabled) return + if (inflightPromise) { inflightPromise.then(() => { try { @@ -482,7 +488,7 @@ export function usePersonalization(): { .finally(() => { inflightPromise = null }) - }, []) + }, [enabled]) return { copy, profession, setProfession } } diff --git a/packages/lib/auth-context.tsx b/packages/lib/auth-context.tsx index 84b095b0..c435a147 100644 --- a/packages/lib/auth-context.tsx +++ b/packages/lib/auth-context.tsx @@ -109,6 +109,27 @@ export function AuthProvider({ children }: { children: ReactNode }) { const orgs = orgsData ?? [] let cancelled = false + const setOrgFromList = (nextOrg: OrganizationListItem) => { + setOrg(nextOrg as unknown as Organization) + } + + const hydrateFullOrg = async () => { + try { + const full = await authClient.organization.getFullOrganization() + if (!cancelled && full?.data) setOrg(full.data) + } catch (error) { + console.error("Failed to hydrate organization:", error) + } + } + + const useListOrgThenHydrate = (nextOrg: OrganizationListItem) => { + if (!cancelled) { + setOrgFromList(nextOrg) + setIsRestoring(false) + } + void hydrateFullOrg() + } + const run = async () => { try { if (orgs.length === 0) { @@ -122,8 +143,7 @@ export function AuthProvider({ children }: { children: ReactNode }) { const one = orgs[0] if (!one) return if (activeOrgId === one.id) { - const full = await authClient.organization.getFullOrganization() - if (!cancelled) setOrg(full?.data ?? null) + useListOrgThenHydrate(one) } else { await setActiveOrg(one.slug) } @@ -135,8 +155,7 @@ export function AuthProvider({ children }: { children: ReactNode }) { const match = orgs.find((o) => o.slug === savedSlug) if (match) { if (activeOrgId === match.id) { - const full = await authClient.organization.getFullOrganization() - if (!cancelled) setOrg(full?.data ?? null) + useListOrgThenHydrate(match) } else { await setActiveOrg(savedSlug) } @@ -148,8 +167,7 @@ export function AuthProvider({ children }: { children: ReactNode }) { if (activeOrgId) { const fromList = orgs.find((o) => o.id === activeOrgId) if (fromList) { - const full = await authClient.organization.getFullOrganization() - if (!cancelled) setOrg(full?.data ?? null) + useListOrgThenHydrate(fromList) return } } diff --git a/packages/lib/posthog.tsx b/packages/lib/posthog.tsx index 540f2ab2..b357d7cb 100644 --- a/packages/lib/posthog.tsx +++ b/packages/lib/posthog.tsx @@ -3,7 +3,7 @@ import { usePathname, useSearchParams } from "next/navigation" import posthog from "posthog-js" import { Suspense, useEffect } from "react" -import { useSession } from "./auth" +import { useAuth } from "./auth-context" function PostHogPageTracking() { const pathname = usePathname() @@ -34,10 +34,23 @@ function PostHogPageTracking() { } export function PostHogProvider({ children }: { children: React.ReactNode }) { - const { data: session } = useSession() + const { user } = useAuth() useEffect(() => { - if (typeof window !== "undefined") { + if (typeof window === "undefined") return + if (posthog.__loaded) { + if (user) { + posthog.identify(user.id, { + email: user.email, + name: user.name, + userId: user.id, + createdAt: user.createdAt, + }) + } + return + } + + const timeout = window.setTimeout(() => { const backendUrl = process.env.NEXT_PUBLIC_BACKEND_URL ?? "https://api.supermemory.ai" @@ -47,22 +60,43 @@ export function PostHogProvider({ children }: { children: React.ReactNode }) { person_profiles: "identified_only", capture_pageview: false, capture_pageleave: true, - loaded: (ph) => ph.register({ app: "app" }), + loaded: (ph) => { + ph.register({ app: "app" }) + if (user) { + ph.identify(user.id, { + email: user.email, + name: user.name, + userId: user.id, + createdAt: user.createdAt, + }) + } + if (process.env.NODE_ENV === "production") { + ph.capture("$pageview", { + $current_url: window.location.href, + path: window.location.pathname, + search_params: window.location.search.replace(/^\?/, ""), + page_type: getPageType(), + org_slug: getOrgSlug(window.location.pathname), + }) + } + }, }) - } - }, []) + }, 1500) + + return () => window.clearTimeout(timeout) + }, [user]) // User identification useEffect(() => { - if (session?.user && posthog.__loaded) { - posthog.identify(session.user.id, { - email: session.user.email, - name: session.user.name, - userId: session.user.id, - createdAt: session.user.createdAt, + if (user && posthog.__loaded) { + posthog.identify(user.id, { + email: user.email, + name: user.name, + userId: user.id, + createdAt: user.createdAt, }) } - }, [session?.user]) + }, [user]) return ( <>