Design an infinitely scrolling feed with ranking freshness and resilient UX.
Quick take: Use cursor pagination and virtualization first. / Split canonical server cache from view model.
Production notes: Use stale-while-revalidate for freshness. / Enforce live region announcements for appended content.
TL;DR
Start with cursor pagination plus virtualization, then layer ranking updates and resilient loading states.
Steel thread
Render first page, append older items via cursor, and preserve scroll anchor.
Interactive views
Use React Flow for CCDAO / data-flow diagrams, Framer Motion for pagination-style transitions, and Lucide for consistent icons (also used in the site chrome).
CCDAO pipeline for this case study
Press enter or space to select a node. You can then use the arrow keys to move the node around. Press delete to remove it and escape to cancel.
Press enter or space to select an edge. You can then press delete to remove it or escape to cancel.
Pagination motion (Framer Motion + spring)
Page 1 — newest cursor `abc123`
1 / 3
C - Collect
Do we require ranking freshness every few seconds?
What is acceptable feed staleness for users on slow networks?
Must keyboard users access new items without losing focus?
C - Component structure
FeedPage orchestrates query state and route filters.
FeedList owns virtualization window.
FeedItem handles local interaction state only.
D - Data modeling
Canonical entity map keyed by postId.
Ordered postIds list per filter for rendering.
Cursor checkpoints stored by filter key.
A - API design
GET /feed?cursor=...&limit=...&filter=...
POST /reactions with idempotency key.
Optional SSE channel for lightweight ranking refresh events.
O - Optimization
Virtualize beyond 40 visible cards.
Keep image placeholders fixed-size to avoid layout shift.
Batch feed update announcements in one polite live region message.
Pattern links
virtualization-strategies: keeps render cost stable as items grow.
pagination-offset-cursor-infinite: protects ordering consistency during inserts.
optimistic-ui-rollback: keeps reaction latency low without data corruption.
Failure modes
Stale response applies after a newer cursor request and reorders visible items.
Personalization timeout returns sparse data and causes sudden content gaps.
Dynamic append announces every item and overwhelms screen-reader users.
Testing checklist
Testing + Accessibility Rubric
Category
Level
Requirement
Done When
functional
unit
State transitions handle loading, success, empty, and error.
Reducer/state machine tests cover all transitions.
functional
integration
Client correctly maps API contracts into UI-ready view models.
Contract fixtures pass with no runtime shape mismatch.
a11y
integration
Full keyboard flow works for primary interaction loop.
Tab/Shift+Tab/Enter/Escape scenarios pass for critical controls.
a11y
e2e
Dynamic updates expose meaningful live region announcements.
Manual SR checks validate announcement timing and text quality.
Explain it clearly
Short version: cursor + virtualization + stale response guard + a11y live region.
Longer version: entity cache shape, pagination contract, and resilience fallback.
Production hardening notes
Track stale-response discard rate and p95 first-scroll hitch in telemetry.
Circuit-break personalization dependencies and degrade to deterministic ordering.
Trade-off Matrix
Add trade-off rows in this section to compare options.