fix: move prefetch out of queryFn into useEffect — zero pagination blocking

The queryFn now only fetches feed events and returns them immediately.
Author and stats prefetching moves to a useEffect in Feed.tsx that fires
after each render where feedItems changes.

The 400px early sentinel means the next page starts fetching while the
user is still reading. The useEffect prefetch runs concurrently — by the
time the user scrolls to new cards, authors are likely already cached.
Cards that miss the window fall back to individual useAuthor queries.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
This commit is contained in:
shakespeare.diy
2026-02-18 10:43:04 -06:00
parent 97520999c0
commit 399397117a
2 changed files with 20 additions and 18 deletions
+12 -1
View File
@@ -9,7 +9,8 @@ import { Button } from '@/components/ui/button';
import { Loader2 } from 'lucide-react';
import LoginDialog from '@/components/auth/LoginDialog';
import SignupDialog from '@/components/auth/SignupDialog';
import { useFeed } from '@/hooks/useFeed';
import { useNostr } from '@nostrify/react';
import { useFeed, prefetchFeedData } from '@/hooks/useFeed';
import { useCurrentUser } from '@/hooks/useCurrentUser';
import { cn } from '@/lib/utils';
import type { FeedItem } from '@/hooks/useFeed';
@@ -27,6 +28,7 @@ export function Feed() {
}
}, [user]);
const { nostr } = useNostr();
const queryClient = useQueryClient();
const {
@@ -67,6 +69,15 @@ export function Feed() {
return items;
}, [data?.pages]);
// After each new page renders, prefetch authors and stats in the background.
// Runs after render so it never blocks pagination — the 400px early sentinel
// gives enough lead time for profiles to arrive before the user scrolls to them.
useEffect(() => {
if (feedItems.length > 0) {
prefetchFeedData(feedItems, nostr, queryClient);
}
}, [feedItems, nostr, queryClient]);
const handleLogin = () => {
setLoginDialogOpen(false);
setSignupDialogOpen(false);
+8 -17
View File
@@ -41,15 +41,15 @@ function parseRepostContent(repost: NostrEvent): NostrEvent | undefined {
}
/**
* Fire-and-forget: seed ['author', pubkey] and ['event-stats', id] caches
* for a page of feed items so that NoteCard's per-card hooks resolve from
* cache rather than firing individual relay queries.
* Prefetch author profiles and interaction stats for a set of feed items,
* seeding the TanStack Query cache so per-card hooks resolve without opening
* individual relay subscriptions.
*
* Runs in the background — does NOT block the queryFn from returning items.
* Includes post authors, repost authors, and mentioned p-tag pubkeys so that
* @mentions and reply-to lines also resolve without individual round trips.
* Called from a useEffect in Feed.tsx — runs after render, never blocks
* the queryFn. Authors use a 1500ms timeout (partial results are fine);
* stats are fully fire-and-forget.
*/
async function prefetchPageData(
export function prefetchFeedData(
items: FeedItem[],
nostr: ReturnType<typeof import('@nostrify/react').useNostr>['nostr'],
queryClient: ReturnType<typeof import('@tanstack/react-query').useQueryClient>,
@@ -71,11 +71,8 @@ async function prefetchPageData(
.map((item) => item.event.id)
.filter((id) => queryClient.getQueryData(['event-stats', id]) === undefined);
// Await author prefetch with a short deadline — whatever profiles arrive
// in time get cached; cards whose authors miss the window fall back to
// individual useAuthor queries (acceptable for a minority of cards).
if (pubkeysToFetch.length > 0) {
await nostr.query(
nostr.query(
[{ kinds: [0], authors: pubkeysToFetch }],
{ signal: AbortSignal.timeout(1500) },
).then((profileEvents) => {
@@ -87,7 +84,6 @@ async function prefetchPageData(
}).catch(() => {});
}
// Stats are fire-and-forget — nice to have pre-cached but not worth blocking on.
if (eventIdsToFetch.length > 0) {
nostr.query(
[
@@ -203,11 +199,6 @@ export function useFeed(tab: 'follows' | 'global') {
.map((ev) => ({ event: ev, sortTimestamp: ev.created_at }));
}
// Prefetch authors and stats in parallel before returning.
// This ensures cache is populated before NoteCards mount, so individual
// useAuthor/useEventStats calls hit cache and never open relay subscriptions.
await prefetchPageData(items, nostr, queryClient);
return items;
},
getNextPageParam: (lastPage) => {