InkdownInkdown
Start writing

Frontend Concepts Low Level

2 files·0 subfolders

Shared Workspace

Frontend Concepts Low Level
micro-frontend-architecture.md

pagination

Shared from "Frontend Concepts Low Level" on Inkdown

Pagination — Complete Frontend Deep Dive

Table of Contents

  1. What Pagination Actually Is
  2. Offset-Based Pagination — The Math
  3. Cursor-Based Pagination
  4. The Three UI Patterns
  5. Critical Edge Cases & Logic
  6. URL Synchronization
  7. Windowed Page Numbers
  8. Virtualization vs Pagination
pagination.md
  • Prefetching Strategies
  • State Summary
  • Chat App Pagination
  • Bi-Directional Pagination
  • The "Start at Bottom" Pattern
  • Before/After Cursors (Discord/Slack Style)
  • WebSocket + Paginated API Hybrid
  • Jump to Message / Around Cursor
  • Gap Detection & Filling
  • Scroll Position Preservation
  • Date/Time Group Separators
  • Pagination in Threads
  • Chat Pagination Quick Comparison

  • 1. What Pagination Actually Is

    Pagination is about fetching and displaying a subset of data at a time instead of loading everything at once. The core problem it solves: your dataset can have 10 items or 10 million — you can't render all of them in the DOM.

    There are two fundamental models of pagination:

    • Offset-based (page number): "Give me page 3" → the backend skips (page-1) * pageSize items
    • Cursor-based (pointer): "Give me the next batch after this item" → the backend uses a reference point (usually an ID or timestamp)

    2. Offset-Based Pagination — The Math

    This is the classic "Page 1 of 10" model.

    Core Variables
    JavaScript
    API Call Format
    Plain text

    Or sometimes:

    Plain text
    What the Backend Does
    Sql
    Frontend State to Track
    JavaScript
    When to Re-fetch
    • currentPage changes → new data
    • pageSize changes → reset currentPage to 1, then fetch
    Key Gotcha — The "Page Size Change" Bug

    If user is on page 5 and changes pageSize from 20 to 50, page 5 no longer makes sense. Always reset to page 1 when pageSize changes.

    JavaScript

    3. Cursor-Based Pagination

    Instead of "give me page 3," it's "give me items after this cursor."

    Core Concept
    JSON

    The cursor is an opaque token — the frontend doesn't need to understand it. It just passes it back to the API.

    Frontend State
    JavaScript
    Going Forward
    JavaScript
    Going Backward
    JavaScript
    Why Cursor-Based Is Superior in Many Cases
    AdvantageExplanation
    No drift problemIf new items are inserted while user browses, offset pagination skips/duplicates items. Cursor pagination doesn't.
    PerformanceWHERE id > cursor is O(1) with an index. OFFSET 10000 scans and discards 10,000 rows.
    ConsistencyWorks well with real-time data feeds.
    Trade-off
    • No "jump to page 7" — you can only go next/prev sequentially
    • More complex state management on frontend

    4. The Three UI Patterns

    Pattern A: Classic Page Numbers
    Plain text
    • Works with offset-based pagination
    • User can jump to any page
    • Must know totalPages upfront
    Pattern B: Load More / "Show More"
    Plain text
    • Accumulates data: page 1 + page 2 + page 3...
    • State: allData = [...allData, ...newPage]
    • Common in social media feeds
    • URL doesn't change, bad for deep linking
    JavaScript
    Pattern C: Infinite Scroll
    • Detect when user scrolls near bottom → fetch next page
    • Same accumulation as Load More
    • Intersection Observer is the modern approach:
    JavaScript
    The Sentinel Element Pattern

    Place an invisible <div> at the end of your list. When it enters the viewport, trigger the next fetch.

    JavaScript

    This is way better than scroll event listeners because:

    • No performance issues from firing on every scroll frame
    • No manual scroll position math
    • Browser handles viewport detection natively

    5. Critical Edge Cases & Logic

    Empty Pages After Deletion

    If you're on page 5 and delete the last item on that page → you're now on an empty page.

    Fix: After deletion, check if current page has 0 items AND currentPage > 1 → go to currentPage - 1.

    JavaScript
    Total Count Is Expensive

    SELECT COUNT(*) on a 10M row table is slow. Options:

    • Use an estimated count (Postgres: reltuples from pg_class)
    • Cache the count and invalidate on inserts/deletes
    • Just use hasMore: boolean instead of exact total (cursor-based does this naturally)
    Race Conditions with Fast Clicking

    User clicks page 2, then page 3 quickly. Page 2's response arrives after page 3's → wrong data shown.

    Fix 1 — Disable buttons while loading:

    JavaScript

    Fix 2 — Abort stale requests:

    JavaScript

    Fix 3 — Request ID tracking:

    JavaScript
    Optimistic Page Transitions

    Show the new page's skeleton/loading state immediately. Don't keep showing old data. Users hate seeing stale content flash while "loading..."


    6. URL Synchronization

    Pagination state should be in the URL. Why:

    • Shareable links ("page 3 of search results")
    • Browser back/forward works correctly
    • Page refresh preserves position
    Two Approaches

    Query params (most common):

    Plain text

    Path segments (less common, mostly for blog/article pages):

    Plain text
    Syncing State with URL
    JavaScript
    In React Router
    JavaScript

    Gotcha: Don't put pagination in URL hash (#page=3) — it doesn't trigger navigation events consistently and isn't sent to the server.


    7. Windowed Page Numbers

    When you have 500 pages, you can't render [1][2][3]...[500]. You need a sliding window.

    The Algorithm
    JavaScript

    This is the exact logic behind every pagination component (MUI, Ant Design, shadcn — they all implement this).

    Visual Output
    Plain text

    8. Virtualization vs Pagination

    These solve the same problem differently:

    AspectPaginationVirtualization
    Data loadedSubset from serverAll data in memory
    DOM nodesOnly current pageOnly visible rows
    Use whenDataset is huge (100K+)Dataset fits in memory (~10K or less)
    ScrollbarDiscrete pagesContinuous scroll
    LibrariesManual / API-drivenreact-window, react-virtuoso, @tanstack/virtual

    You can also combine them: paginate to load chunks from server, then virtualize within each chunk. This is what tools like AG Grid and large data tables do.


    9. Prefetching Strategies

    Next-Page Prefetch

    When user is on page 1, prefetch page 2 in the background:

    JavaScript
    Prefetch on Hover

    When mouse hovers over "Next" button, prefetch. This gives 200-500ms of head start:

    JavaScript
    Prefetch on Focus (for keyboard users)
    JavaScript

    10. State Summary

    For Offset-Based Pagination
    JavaScript
    For Cursor-Based Pagination
    JavaScript

    11. Chat App Pagination

    Chat apps have a fundamentally different pagination model than typical list pagination. This section covers everything unique to chat-style interfaces.


    12. Bi-Directional Pagination

    Normal pagination = one direction (next page). Chat = two directions simultaneously:

    • Scroll up → load older messages (back in time)
    • Scroll down → load newer messages (forward in time)
    Plain text
    What You Track
    JavaScript

    13. The "Start at Bottom" Pattern

    Chat apps don't start at page 1. They start at the most recent messages. This is inverted from normal pagination.

    Initial Load
    JavaScript

    The API returns 50 most recent messages. You render them reversed (oldest on top, newest on bottom) even though they came back newest-first.

    Then Scrolling Up
    JavaScript

    14. Before/After Cursors (Discord/Slack Style)

    This is the dominant pattern in chat. Instead of numeric page offsets, you use message IDs or timestamps as anchors.

    Discord's Approach (Snowflake IDs)
    Plain text
    • before → messages older than this ID
    • after → messages newer than this ID
    • around → messages centered around this ID (used for "jump to message")
    Slack's Approach (Timestamp Cursors)
    Plain text
    Why IDs/Timestamps Over Offsets
    • Messages get deleted → offsets shift, but IDs stay stable
    • New messages arrive in real-time → offsets become stale instantly
    • Cursor is always valid even if the dataset changes

    15. WebSocket + Paginated API Hybrid

    This is the real architecture behind every chat app:

    Plain text
    How They Cooperate
    1. On mount: Fetch last 50 messages via paginated API
    2. While alive: New messages arrive via WebSocket → append to message list
    3. Scroll up: Fetch older history via API with cursor
    4. Conflict resolution: When API returns old messages, ensure WebSocket messages received in the meantime aren't duplicated
    Deduplication Logic
    JavaScript
    Complete Flow
    JavaScript

    16. Jump to Message / around Cursor

    User clicks a notification for a message from 3 months ago. You need to load context around it:

    JavaScript

    This returns ~25 messages before + ~25 after, centered on the target.

    Frontend Steps
    1. Fetch messages around the target
    2. Render them
    3. Scroll to highlight/position the target message in view
    4. Set up cursors in both directions for further scrolling
    JavaScript

    17. Gap Detection & Filling

    When user jumps to a message (via around), there's now a gap between the loaded window and previously loaded messages.

    Plain text
    Tracking Loaded Ranges
    JavaScript
    Filling the Gap

    When user scrolls toward a gap, fetch the missing range:

    JavaScript
    Gap Detection Logic
    JavaScript

    18. Scroll Position Preservation

    The hardest part of chat pagination. When you prepend older messages (user scrolled up), the browser scroll position jumps. You need to preserve the user's perceived position.

    The Problem
    Plain text
    The Fix — Measure, Prepend, Adjust
    JavaScript
    Why requestAnimationFrame?

    The DOM hasn't updated yet when setMessages is called. requestAnimationFrame fires after the browser has painted the new content, so scrollHeight reflects the new messages.

    Libraries That Handle This
    • react-virtuoso — has built-in initialTopMostItemIndex and scroll preservation
    • react-window — requires manual handling with scrollToItem
    • @tanstack/virtual — provides scrollPadding and measurement APIs

    19. Date/Time Group Separators

    Not pagination per se, but closely related — inserting date headers between messages:

    Plain text
    Logic
    JavaScript

    Date separators count toward your visual layout but NOT your API limit.


    20. Pagination in Threads

    Threads (like Slack threads) are pagination within pagination:

    • Main channel: paginated by message ID
    • Thread: independently paginated, anchored to the parent message
    JavaScript

    Each thread has its own cursor state, independent of the parent channel.

    State for Threads
    JavaScript

    21. Chat Pagination Quick Comparison

    PatternUsed ByDirectionCursor Type
    Offset/LimitE-commerce, admin panelsForward only?page=3
    Before/AfterDiscord, Slack, WhatsAppBi-directionalMessage ID or timestamp
    AroundDiscord (jump-to-msg)CenteredMessage ID
    Timestamp windowSlackBi-directionalUnix timestamp
    WebSocket + APIEvery real-time chatHybridN/A (push) + cursor

    Key Takeaways

    1. Offset pagination is simple but breaks with real-time data and performs poorly at scale
    2. Cursor pagination is the modern standard for feeds, chats, and infinite lists
    3. Chat apps combine bi-directional cursor pagination with WebSocket for real-time updates
    4. Scroll position preservation is the single hardest engineering challenge in chat pagination
    5. URL sync is essential for any paginated view that users might bookmark or share
    6. Deduplication is critical when mixing real-time and paginated data
    7. Gap detection matters when users can jump to arbitrary messages