perf(web): defer non-critical app load work

This commit is contained in:
ved015 2026-06-10 14:10:29 +05:30
parent 9a1bab7182
commit a8be1d2d1d
8 changed files with 145 additions and 36 deletions

View file

@ -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<MemoryOfDay | null>({
@ -393,7 +406,7 @@ export default function NewPage() {
},
staleTime: 24 * 60 * 60 * 1000,
refetchOnWindowFocus: false,
enabled: !!user,
enabled: loadHomeAsyncData && !!user,
})
useHotkeys("c", () => {

View file

@ -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<DocumentsResponse> => {
@ -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({
<div className="flex gap-4 items-start">
<div className="flex-[4] min-w-0">
{isRecentsLoading ? (
{showRecentsLoading ? (
<ul
className="space-y-0.5"
aria-busy="true"
@ -1567,7 +1581,7 @@ export function DashboardView({
</div>
</div>
{(isRecentsLoading || recents.length === 0) && (
{(showRecentsLoading || recents.length === 0) && (
<div className="space-y-2 sm:hidden">
<p className="text-[10px] font-medium uppercase tracking-[0.12em] text-fg-faint">
Suggested for you

View file

@ -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()

View file

@ -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,
}
}

View file

@ -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,
})
}

View file

@ -401,7 +401,11 @@ function classifyProfession(results: SearchResult[]): Profession {
let inflightPromise: Promise<void> | 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 }
}

View file

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

View file

@ -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 (
<>