Skip to main content

SECTION 0 — WHY FEEDS ARE HARD

A “feed page” looks like UI work.

In production, it’s a multi-system problem:

  • latency budgets (TTFB + render + interaction)

  • caching and staleness tolerance

  • pagination correctness

  • ranking consistency

  • partial failure handling


SECTION 1 — REQUIREMENTS

Functional

  • Show a personalized list of items.

  • Support infinite scroll.

  • Allow filtering/sorting.

  • Clicking an item opens a detail view quickly.

Non-functional

  • Performance: fast first content, smooth scrolling.

  • Correctness: no duplicates/missing items across pages.

  • Reliability: degrade gracefully if personalization service fails.

  • Cost: avoid per-request expensive ranking.


SECTION 2 — INVARIANTS

  1. Pagination must be deterministic for a given cursor.

  2. A page fetch must be idempotent (retries don’t create duplicates in UI state).

  3. UI must represent “staleness” explicitly when applicable.


SECTION 3 — RENDERING STRATEGY: SSR / ISR / CSR

Decision inputs:

  • personalization strength

  • SEO needs

  • freshness requirements

  • backend latency

Practical default (senior-friendly)

  • SSR for shell + first page when you need fast first content and SEO.

  • CSR for subsequent pages (infinite scroll).

  • ISR only when feed is mostly cacheable (e.g., “trending”, not deeply personalized).

Senior rule:

Split the page: render what’s stable, fetch what’s personal.


SECTION 4 — API CONTRACT (CURSOR-BASED PAGINATION)

  • GET /feed?limit=25&cursor=...&filter=...

Response:

  • items: FeedItem[]

  • nextCursor: string | null

  • generatedAt: ISO timestamp

  • stale: boolean (optional)

Cursor design:

  • opaque to clients

  • encodes sort key + tie-breaker (e.g., (score, createdAt, id))

  • includes filter hash to prevent mismatched cursors

Avoid offset pagination for large feeds.


SECTION 5 — CACHING STRATEGY (HIERARCHY)

Cache layers:

  1. Browser cache (short, for assets)

  2. CDN (only for cacheable variants like “trending”)

  3. App cache (per-user feed segments if feasible)

  4. DB / search index (precomputed or query-optimized)

Common senior pattern:

  • Cache ranked ID lists (cheap to serve)

  • Fetch item details by IDs (batched)

This reduces cost and improves tail latency.


SECTION 6 — STALENESS + CONSISTENCY

Consistency options:

  • Snapshot feed: user sees a consistent list for a time window.

  • Live feed: list changes as new items arrive.

Senior default:

  • Snapshot for pagination correctness.

  • Provide “new posts available” banner to refresh snapshot.

Mechanism:

  • server issues a feedSessionId or includes generatedAt

  • cursor ties to that snapshot


SECTION 7 — FRONTEND STATE MACHINE

IDLE
-> LOADING_FIRST_PAGE
-> READY
-> LOADING_NEXT_PAGE
-> READY
-> ERROR (retry)

READY
-> REFRESHING (new snapshot)

Senior UX rules:

  • De-dupe items by stable key.

  • Keep scroll position stable.

  • Use skeletons for first load, inline spinners for next page.


SECTION 8 — FAILURE MODES

  • Ranking service slow → fallback to recency feed

  • Partial item details missing → render placeholders for those cards

  • Cursor invalid → reset and show “feed updated, reloading”

  • Cache stampede on trending → request coalescing + jitter


SECTION 9 — OBSERVABILITY

End-to-end metrics:

  • TTFB p50/p95

  • INP / interaction latency

  • feed API latency + error rate

  • cache hit rate for ranked lists

  • duplicates detected (should be near zero)


SECTION 10 — EXERCISES

  1. Pick SSR vs CSR vs ISR for your feed and justify with constraints.

  2. Design an opaque cursor payload (fields + tie-breaker).

  3. Define staleness semantics and what UI communicates.

  4. Propose a caching plan that reduces ranking cost.


🏁 END — FEED PAGE CASE STUDY