From 134411f7f839009899e46df586700f558d0500a9 Mon Sep 17 00:00:00 2001 From: 134 Date: Tue, 7 Jul 2026 22:16:07 +0800 Subject: [PATCH] perf: improve home and canvas initial load --- .../[id]/CanvasEditorLegacyPageClient.tsx | 12 +++-- components/home/lovart-home.tsx | 46 +++++++++++++++++-- .../home/lovart/lovart-featured-gallery.tsx | 2 + .../home/lovart/lovart-hero-composer.tsx | 15 +++++- .../home/lovart/lovart-projects-section.tsx | 2 + 5 files changed, 68 insertions(+), 9 deletions(-) diff --git a/app/canvas/[id]/CanvasEditorLegacyPageClient.tsx b/app/canvas/[id]/CanvasEditorLegacyPageClient.tsx index 8e501117e..2a48e250c 100644 --- a/app/canvas/[id]/CanvasEditorLegacyPageClient.tsx +++ b/app/canvas/[id]/CanvasEditorLegacyPageClient.tsx @@ -1976,7 +1976,7 @@ export default function CanvasEditorLegacyPage({ ); const [activeWorkflowExecutionTaskKey, setActiveWorkflowExecutionTaskKey] = useState(null); - const [isPanelOpen, setIsPanelOpen] = useState(() => !isVideoMode); + const [isPanelOpen, setIsPanelOpen] = useState(() => requestedAgentMode); const [floatingPanelWidth, setFloatingPanelWidth] = useState(FLOATING_PANEL_WIDTH); const [ @@ -3280,11 +3280,15 @@ export default function CanvasEditorLegacyPage({ }, [checkCanvasAuthSession]); useEffect(() => { + if (!isPanelOpen && !isVideoCanvasFileListOpen) { + return; + } + const timeoutId = window.setTimeout(() => { setAreSecondaryPanelsReady(true); - }, 800); + }, 120); return () => window.clearTimeout(timeoutId); - }, []); + }, [isPanelOpen, isVideoCanvasFileListOpen]); const hydrateCanvasProject = useCallback( (project: CanvasInitialProject) => { @@ -10844,7 +10848,7 @@ export default function CanvasEditorLegacyPage({ ) : null}
- {!areSecondaryPanelsReady ? ( + {!isPanelOpen ? null : !areSecondaryPanelsReady ? ( ) : panelMode === "chat" ? ( // AI 对话内容 - 使用 key 强制在 id 变化时重新挂载 diff --git a/components/home/lovart-home.tsx b/components/home/lovart-home.tsx index 5c9565254..6751449a0 100644 --- a/components/home/lovart-home.tsx +++ b/components/home/lovart-home.tsx @@ -2,7 +2,6 @@ import { useCallback, useEffect, useMemo, useRef, useState } from "react"; import { useRouter } from "next/navigation"; -import { signIn, signOut } from "next-auth/react"; import { useTheme } from "next-themes"; import useSWR from "swr"; import useSWRInfinite from "swr/infinite"; @@ -60,7 +59,6 @@ import { getCanvasLaunchStorageKey, } from "@/lib/canvas-launch"; import type { MembershipConfig } from "@/lib/membership/config"; -import { fetchUploadWithRetry } from "@/lib/client/upload-with-retry"; import { normalizeLovartHomeSidebarConfig, type LovartHomeSidebarItemKey, @@ -465,6 +463,7 @@ export function LovartHome({ const [previewCursorVisible, setPreviewCursorVisible] = useState(true); const [isSecondaryDataReady, setIsSecondaryDataReady] = useState(false); const [isFeaturedDataReady, setIsFeaturedDataReady] = useState(false); + const [isHomeAnimationReady, setIsHomeAnimationReady] = useState(false); const projectSectionRef = useRef(null); const featuredSectionRef = useRef(null); const featuredLoadMoreRef = useRef(null); @@ -535,6 +534,31 @@ export function LovartHome({ }; }, [isFeaturedDataReady]); + useEffect(() => { + if (typeof window === "undefined" || !isSecondaryDataReady) { + return; + } + + const loadHomeAnimation = () => setIsHomeAnimationReady(true); + const idleWindow = window as Window & { + cancelIdleCallback?: (handle: number) => void; + requestIdleCallback?: ( + callback: IdleRequestCallback, + options?: IdleRequestOptions + ) => number; + }; + + if (typeof idleWindow.requestIdleCallback === "function") { + const idleId = idleWindow.requestIdleCallback(loadHomeAnimation, { + timeout: 3200, + }); + return () => idleWindow.cancelIdleCallback?.(idleId); + } + + const timer = window.setTimeout(loadHomeAnimation, 1800); + return () => window.clearTimeout(timer); + }, [isSecondaryDataReady]); + const { data: canvasData, mutate: mutateCanvas } = useSWR<{ success: boolean; data: CanvasProject[]; @@ -671,7 +695,10 @@ export function LovartHome({ enabled?: boolean; htmlContent?: string; fileName?: string; - }>(isSecondaryDataReady ? "/api/home-animation" : null, fetcher); + }>( + isSecondaryDataReady && isHomeAnimationReady ? "/api/home-animation" : null, + fetcher + ); const membershipConfig = membershipData?.config; const membershipPlans = membershipConfig?.plans || []; @@ -896,6 +923,7 @@ export function LovartHome({ } catch (_error) {} try { + const { signOut } = await import("next-auth/react"); await signOut({ callbackUrl: "/", redirect: false, @@ -1109,6 +1137,9 @@ export function LovartHome({ }; const uploadSelectedFiles = async (): Promise => { + const { fetchUploadWithRetry } = await import( + "@/lib/client/upload-with-retry" + ); const result: CanvasLaunchAttachment[] = []; for (const item of files) { @@ -1146,6 +1177,7 @@ export function LovartHome({ setIsLoggingIn(true); try { + const { signIn } = await import("next-auth/react"); const result = await signIn("credentials", { email, password: loginPassword, @@ -1171,6 +1203,7 @@ export function LovartHome({ toast.error("Google 登录未启用"); return; } + const { signIn } = await import("next-auth/react"); await signIn("google", { callbackUrl: "/" }); }; @@ -1226,6 +1259,9 @@ export function LovartHome({ const blob = await response.blob(); const formData = new FormData(); formData.append("file", blob, `edited-${Date.now()}.png`); + const { fetchUploadWithRetry } = await import( + "@/lib/client/upload-with-retry" + ); const uploadResponse = await fetchUploadWithRetry(formData); if (!uploadResponse.ok) { @@ -1802,7 +1838,9 @@ export function LovartHome({ heroPromoText={heroPromoText} heroSubtitleText={heroSubtitleText} heroTitleText={heroTitleText} - homeAnimationData={homeAnimationData} + homeAnimationData={ + isHomeAnimationReady ? homeAnimationData : undefined + } isAgentModeActive={isComposerAgentModeActive} isMembershipEnabled={isMembershipEnabled} isSubmitting={isSubmitting} diff --git a/components/home/lovart/lovart-featured-gallery.tsx b/components/home/lovart/lovart-featured-gallery.tsx index f038c1dd1..456f594d6 100644 --- a/components/home/lovart/lovart-featured-gallery.tsx +++ b/components/home/lovart/lovart-featured-gallery.tsx @@ -62,6 +62,8 @@ export function LovartFeaturedGallery({ {item.prompt
diff --git a/components/home/lovart/lovart-hero-composer.tsx b/components/home/lovart/lovart-hero-composer.tsx index c5f491c59..9f5e73568 100644 --- a/components/home/lovart/lovart-hero-composer.tsx +++ b/components/home/lovart/lovart-hero-composer.tsx @@ -1,6 +1,6 @@ +import dynamic from "next/dynamic"; import { ArrowUp, Box, Lightbulb, Loader2, Plus, Sparkles } from "lucide-react"; import type { ChangeEvent } from "react"; -import { HomeAnimationEmbed } from "@/components/home/home-animation-embed"; import { Button } from "@/components/ui/button"; import { Select, @@ -12,6 +12,19 @@ import { Textarea } from "@/components/ui/textarea"; import { cn } from "@/lib/utils"; import type { ModelOption, UploadFileItem } from "./types"; +const HomeAnimationEmbed = dynamic( + () => + import("@/components/home/home-animation-embed").then( + (module) => module.HomeAnimationEmbed + ), + { + loading: () => ( +
+ ), + ssr: false, + } +); + type LovartHeroComposerProps = { composerPlaceholder: string; displayLogo: string; diff --git a/components/home/lovart/lovart-projects-section.tsx b/components/home/lovart/lovart-projects-section.tsx index 36ab66f87..28dad8ecf 100644 --- a/components/home/lovart/lovart-projects-section.tsx +++ b/components/home/lovart/lovart-projects-section.tsx @@ -76,6 +76,8 @@ export function LovartProjectsSection({ {item.title} ) : (