Defer sidebar queries until feed loads and sidebar is visible

- Add `enabled` parameter to `useTrendingTags` and `useLatestAccounts` hooks
- RightSidebar now detects xl breakpoint visibility with matchMedia
- Sidebar queries only fire after the feed finishes its initial fetch
- On smaller screens where the sidebar is hidden, queries are skipped entirely
- Gives the feed full relay bandwidth priority on page load

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
This commit is contained in:
shakespeare.diy
2026-02-17 03:16:04 -06:00
parent d5f61e1ff8
commit 5babdf6fa0
2 changed files with 43 additions and 5 deletions
+39 -3
View File
@@ -6,7 +6,24 @@ import { useTrendingTags, useLatestAccounts } from '@/hooks/useTrending';
import { genUserName } from '@/lib/genUserName';
import { NSchema as n } from '@nostrify/nostrify';
import { nip19 } from 'nostr-tools';
import { useMemo } from 'react';
import { useMemo, useState, useEffect } from 'react';
import { useIsFetching } from '@tanstack/react-query';
const XL_BREAKPOINT = 1280;
/** Returns true when the viewport is at least the xl breakpoint (1280px). */
function useIsXl(): boolean {
const [isXl, setIsXl] = useState(window.innerWidth >= XL_BREAKPOINT);
useEffect(() => {
const mql = window.matchMedia(`(min-width: ${XL_BREAKPOINT}px)`);
const onChange = () => setIsXl(mql.matches);
mql.addEventListener('change', onChange);
return () => mql.removeEventListener('change', onChange);
}, []);
return isXl;
}
/** Small sparkline SVG for trending tags. */
function TrendSparkline() {
@@ -36,8 +53,27 @@ function TrendSparkline() {
}
export function RightSidebar() {
const { data: trendingTags, isLoading: tagsLoading } = useTrendingTags();
const { data: latestAccounts, isLoading: accountsLoading } = useLatestAccounts();
const isXl = useIsXl();
// Only start sidebar queries once the feed has finished its initial fetch.
// Track: feed must start fetching first, then finish, before we enable sidebar queries.
const feedFetching = useIsFetching({ queryKey: ['feed'] });
const [feedStarted, setFeedStarted] = useState(false);
const [feedHasLoaded, setFeedHasLoaded] = useState(false);
useEffect(() => {
if (!feedStarted && feedFetching > 0) {
setFeedStarted(true);
}
if (feedStarted && !feedHasLoaded && feedFetching === 0) {
setFeedHasLoaded(true);
}
}, [feedFetching, feedStarted, feedHasLoaded]);
const sidebarEnabled = isXl && feedHasLoaded;
const { data: trendingTags, isLoading: tagsLoading } = useTrendingTags(sidebarEnabled);
const { data: latestAccounts, isLoading: accountsLoading } = useLatestAccounts(sidebarEnabled);
return (
<aside className="w-[340px] shrink-0 hidden xl:flex flex-col sticky top-0 h-screen overflow-y-auto pt-6 pb-3 px-6">
+4 -2
View File
@@ -8,7 +8,7 @@ export interface TrendingTag {
}
/** Extracts trending hashtags from recent notes. */
export function useTrendingTags() {
export function useTrendingTags(enabled = true) {
const { nostr } = useNostr();
return useQuery<TrendingTag[]>({
@@ -39,12 +39,13 @@ export function useTrendingTags() {
.sort((a, b) => b.count - a.count)
.slice(0, 5);
},
enabled,
staleTime: 5 * 60 * 1000,
});
}
/** Fetches the latest kind 0 profiles seen on the relay. */
export function useLatestAccounts() {
export function useLatestAccounts(enabled = true) {
const { nostr } = useNostr();
return useQuery<NostrEvent[]>({
@@ -56,6 +57,7 @@ export function useLatestAccounts() {
);
return events.sort((a, b) => b.created_at - a.created_at).slice(0, 5);
},
enabled,
staleTime: 5 * 60 * 1000,
});
}