From 129b826c14da2b456c5611b5cedbce53bf9278a7 Mon Sep 17 00:00:00 2001 From: Philippe Serhal Date: Sat, 10 Oct 2026 22:31:42 -0400 Subject: [PATCH] perf: lazily resolve install size and vulns Both of these are intensive and scale with package size and/or number of releases and/or number of transitive dependencies. Neither should really block the page load. Both should be cached. In the worst case scenario in my testing, this cut 55% of the cold render ttfb for a package page. --- .../DepsStats/DependencyStatsPanel.vue | 17 +++--- app/composables/useDependencyAnalysis.ts | 11 ++++ app/pages/package/[[org]]/[name].vue | 60 +++++++++++-------- 3 files changed, 55 insertions(+), 33 deletions(-) diff --git a/app/components/DepsStats/DependencyStatsPanel.vue b/app/components/DepsStats/DependencyStatsPanel.vue index 3b7291a0ed..7dd1cacdea 100644 --- a/app/components/DepsStats/DependencyStatsPanel.vue +++ b/app/components/DepsStats/DependencyStatsPanel.vue @@ -34,13 +34,16 @@ const { data: installSize, execute: fetchInstallSize } = useLazyFetch { - if (version) fetchInstallSize() - }, - { immediate: true }, -) +// Client-only: install size walks the whole dependency tree, so never block SSR on it. +if (import.meta.client) { + watch( + resolvedVersion, + version => { + if (version) fetchInstallSize() + }, + { immediate: true }, + ) +} const { diff: sizeDiff } = useInstallSizeDiff( () => props.packageName, diff --git a/app/composables/useDependencyAnalysis.ts b/app/composables/useDependencyAnalysis.ts index d8eee606a2..5de41ee261 100644 --- a/app/composables/useDependencyAnalysis.ts +++ b/app/composables/useDependencyAnalysis.ts @@ -2,6 +2,13 @@ * Shared composable for dependency analysis data (vulnerabilities, deprecated packages). * Fetches once and caches the result so multiple components can use it. * Before: useVulnerabilityTree - but now we use this for both vulnerabilities and deprecated packages. + * + * Client-only and lazy: resolving the whole transitive dependency graph (one packument download + * per dependency) plus OSV queries is far too slow to block the package page's server render. The + * data only drives the vulnerability count and the vulnerability/deprecated trees, none of which + * need to be in the server HTML. + * + * `useFetch` dedupes by key (the request URL), so all consumers share the same request/result. */ export function useDependencyAnalysis( packageName: MaybeRefOrGetter, @@ -10,5 +17,9 @@ export function useDependencyAnalysis( return useFetch( () => `/api/registry/vulnerabilities/${encodePackageName(toValue(packageName))}/v/${toValue(version)}`, + { + server: false, + lazy: true, + }, ) } diff --git a/app/pages/package/[[org]]/[name].vue b/app/pages/package/[[org]]/[name].vue index 1cb7073cb8..ac88e3c315 100644 --- a/app/pages/package/[[org]]/[name].vue +++ b/app/pages/package/[[org]]/[name].vue @@ -128,16 +128,19 @@ const { }, ) -// Trigger fetch only when we have the real resolved version -watch( - [resolvedVersion, resolvedStatus], - ([version, status]) => { - if (version && status === 'success') { - fetchInstallSize() - } - }, - { immediate: true }, -) +// Trigger fetch only when we have the real resolved version. +// Client-only: install size walks the whole dependency tree, so never block SSR on it. +if (import.meta.client) { + watch( + [resolvedVersion, resolvedStatus], + ([version, status]) => { + if (version && status === 'success') { + fetchInstallSize() + } + }, + { immediate: true }, + ) +} const { data: skillsData } = useLazyFetch( () => { @@ -672,23 +675,28 @@ const showSkeleton = shallowRef(false) - - - +