# Infinite Scroll with Convex This guide explains how to use `@humanspeak/svelte-virtual-list` with [Convex](https://convex.dev) for real-time data and infinite scroll pagination. ## Overview This pattern combines: 1. **Real-time subscriptions** - First page updates live via Convex WebSocket 2. **Infinite scroll pagination** - Older pages loaded on-demand via one-time queries 3. **Virtualization** - Only visible items are rendered in the DOM ## Architecture ```text ┌─────────────────────────────────────────────────────────┐ │ VirtualList │ │ ┌───────────────────────────────────────────────────┐ │ │ │ Live Data (useQuery - WebSocket subscription) │ │ │ │ - First page of items │ │ │ │ - Updates in real-time when data changes │ │ │ └───────────────────────────────────────────────────┘ │ │ ┌───────────────────────────────────────────────────┐ │ │ │ Paginated Data (client.query - one-time fetch) │ │ │ │ - Loaded when user scrolls near bottom │ │ │ │ - Uses cursor-based pagination │ │ │ └───────────────────────────────────────────────────┘ │ └─────────────────────────────────────────────────────────┘ ``` --- ## Setup ### 1. Install Dependencies ```bash pnpm add convex convex-svelte @humanspeak/svelte-virtual-list ``` ### 2. Environment Variables ```env PUBLIC_CONVEX_URL=https://your-deployment.convex.cloud ``` ### 3. Initialize Convex Client Create `src/lib/convex.ts`: ```typescript import { env } from '$env/dynamic/public' import { setupConvex, useConvexClient } from 'convex-svelte' export const setup = () => { if (env.PUBLIC_CONVEX_URL) { setupConvex(env.PUBLIC_CONVEX_URL, { unsavedChangesWarning: false }) } } export const getConvexClient = () => useConvexClient() ``` Call `setup()` in your root layout: ```svelte {@render children()} ``` --- ## Convex Backend ### Real-time Query (for live first page) ```typescript // convex/items.ts import { query } from './_generated/server' import { v } from 'convex/values' export const listRecent = query({ args: { limit: v.optional(v.number()) }, handler: async (ctx, args) => { const limit = args.limit ?? 50 return await ctx.db.query('items').order('desc').take(limit) } }) ``` ### Paginated Query (for infinite scroll) ```typescript // convex/items.ts export const listPaginated = query({ args: { cursor: v.optional(v.number()), limit: v.optional(v.number()) }, handler: async (ctx, args) => { const limit = args.limit ?? 50 const cursor = args.cursor let queryBuilder = ctx.db.query('items') if (cursor !== undefined) { queryBuilder = queryBuilder.filter((q) => q.lt(q.field('_creationTime'), cursor)) } const items = await queryBuilder.order('desc').take(limit + 1) const hasMore = items.length > limit const pageItems = hasMore ? items.slice(0, limit) : items return { items: pageItems, hasMore, nextCursor: pageItems.length > 0 ? pageItems[pageItems.length - 1]._creationTime : null } } }) ``` --- ## Frontend Implementation ### Complete Example ```svelte {#if isLive}