mirror of
https://github.com/supermemoryai/supermemory.git
synced 2026-09-09 22:31:10 +00:00
perf(web): defer non-critical app load work
This commit is contained in:
parent
9a1bab7182
commit
a8be1d2d1d
8 changed files with 145 additions and 36 deletions
|
|
@ -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", () => {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 (
|
||||
<>
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue