Rendering every feed item to the DOM at once sends scroll performance into the ground once a user accumulates a few hundred posts — main-thread jank on scroll, layout thrash on image loads, and memory spikes on mobile that trigger tab kills. A Load More button solves the data problem but still leaves the old DOM nodes parked in memory, and a naive infinite scroll without windowing just accelerates the collapse. Engagement metrics track directly to feed responsiveness.
Low because the feed still functions — degradation is felt only on long sessions with heavy feeds, not on first load.
Wire an Intersection Observer sentinel to fetchNextPage from useInfiniteQuery, and wrap the rendered list in a windowing component like react-virtual or FixedSizeList so only the viewport-visible rows live in the DOM. This keeps scroll at 60fps regardless of feed length. See components/Feed.tsx:
const { data, fetchNextPage, hasNextPage } = useInfiniteQuery({ queryKey: ['feed'], queryFn, getNextPageParam: p => p.nextCursor })
const { ref } = useInView({ onChange: v => v && hasNextPage && fetchNextPage() })
ID: community-social-engagement.activity-feeds.infinite-scroll-ux
Severity: low
What to look for: Enumerate all relevant files and Examine feed components for infinite scroll implementation. Look for libraries like react-intersection-observer, useInfiniteQuery (TanStack Query), or custom Intersection Observer setup. Check for virtual scrolling/windowing libraries like react-virtual if the feed items are complex DOM elements. Verify that the feed can handle hundreds of items without browser lag.
Pass criteria: Infinite scroll is implemented using an observer pattern (Intersection Observer API or library). For complex feed items, virtual scrolling is used. The feed component does not render all items to the DOM at once.
Fail criteria: No infinite scroll, or all feed items are rendered to the DOM (causing performance issues as the feed grows).
Skip (N/A) when: Feed is paginated (not infinite scroll), or feed is small enough to render all at once.
Detail on fail: "Feed renders all 500 items to DOM with no virtual scrolling — scrolling is laggy" or "No infinite scroll implemented; user must click 'Load More' button"
Remediation: Implement infinite scroll with virtual rendering:
// components/Feed.tsx
'use client'
import { useInfiniteQuery } from '@tanstack/react-query'
import { useInView } from 'react-intersection-observer'
import { useEffect } from 'react'
import { FixedSizeList } from 'react-virtual'
export function Feed() {
const { data, fetchNextPage, hasNextPage } = useInfiniteQuery({
queryKey: ['feed'],
queryFn: async ({ pageParam }) => {
const res = await fetch(`/api/feed?cursor=${pageParam}`)
return res.json()
},
initialPageParam: null,
getNextPageParam: (lastPage) => lastPage.nextCursor
})
const { ref } = useInView({
onChange: (inView) => inView && hasNextPage && fetchNextPage()
})
const items = data?.pages.flatMap(p => p.posts) || []
return (
<>
<FixedSizeList
height={800}
itemCount={items.length}
itemSize={100}
width="100%"
>
{({ index, style }) => (
<div style={style}>
<PostCard post={items[index]} />
</div>
)}
</FixedSizeList>
<div ref={ref} />
</>
)
}