Communitygithub.com

YepAPI/skills

カーソルベースのページネーション、IntersectionObserver、@tanstack/react-virtual による仮想化リスト、スクロール位置の復元

skills とは?

このスキルは、大規模なリストや無限スクロールUIの効率的な実装に焦点を当てています。主にReact QueryとIntersectionObserverを組み合わせ、カーソルベースのページネーションを採用することで、データの挿入や削除が発生しても安定したページネーションを実現します。また、@tanstack/react-virtualを使用した仮想化により、数千から数万件のアイテムを扱う場合でも、DOMに表示されている行のみをレンダリングしてパフォーマンスを維持します。さらに、ローディングスケルトン、手動ロード用の「さらに読み込む」ボタン、空の状態やリスト終了時の表示、スクロール位置の復元など、ユーザー体験を向上させるベストプラクティスを網羅しています。これらのパターンは、Cursor、Claude Code、CodexなどのAIエージェントがコード生成時に従うべきルールとして提供されています。

対応~Claude Code~Codex CLI✓Cursor
npx skills add https://github.com/YepAPI/skills/tree/HEAD/skills/infinite-scroll

お気に入りのAIに質問する

このエージェントスキルを事前に読み込んだ状態で新しいチャットを開きます。

ドキュメント

無限スクロール

ルール

  • カーソルベースのページネーション: 最後のアイテムのIDまたはタイムスタンプをカーソルとして使用 — オフセットは挿入/削除で破綻するため使用しない
  • IntersectionObserver: リスト下部のセンチネル要素にアタッチ — 可視になったらフェッチをトリガー
  • 仮想化: 1,000件以上のリストには @tanstack/react-virtual を使用 — 表示行のみをDOMにレンダリング
  • ローディングスケルトン: 次のページをフェッチ中は下部にプレースホルダーアイテムを表示 — スピナーは使わない
  • 「さらに読み込む」ボタン: 手動制御を好むユーザーや低速接続のユーザー向けのフォールバックとして提供
  • スクロール位置の復元: ナビゲーション前にスクロール位置を保存し、戻る時に復元 — sessionStorage またはルーターの状態を使用
  • 空の状態: リストにゼロ件のときにメッセージを表示 — 空白ページにしない
  • リストの終了: hasNextPage が false のとき「最後まで表示しました」と表示 — オブザーバーを停止

パターン

// React Query + IntersectionObserver による無限スクロール
import { useInfiniteQuery } from "@tanstack/react-query";
import { useRef, useEffect } from "react";

function InfiniteList() {
  const sentinelRef = useRef<HTMLDivElement>(null);
  const { data, fetchNextPage, hasNextPage, isFetchingNextPage } = useInfiniteQuery({
    queryKey: ["items"],
    queryFn: ({ pageParam }) => fetchItems({ cursor: pageParam, limit: 20 }),
    getNextPageParam: (lastPage) => lastPage.nextCursor ?? undefined,
    initialPageParam: undefined as string | undefined,
  });

  useEffect(() => {
    const observer = new IntersectionObserver(
      (entries) => { if (entries[0].isIntersecting && hasNextPage) fetchNextPage(); },
      { rootMargin: "200px" } // ユーザーが下部に到達する前にプリフェッチ
    );
    if (sentinelRef.current) observer.observe(sentinelRef.current);
    return () => observer.disconnect();
  }, [hasNextPage, fetchNextPage]);

  const allItems = data?.pages.flatMap((page) => page.items) ?? [];

  return (
    <div>
      {allItems.map((item) => <ItemCard key={item.id} item={item} />)}
      {isFetchingNextPage && <LoadingSkeleton count={3} />}
      {!hasNextPage && allItems.length > 0 && <p>最後まで表示しました</p>}
      {allItems.length === 0 && !isFetchingNextPage && <EmptyState />}
      <div ref={sentinelRef} />
    </div>
  );
}

// 1万件以上のアイテム向け仮想化リスト
import { useVirtualizer } from "@tanstack/react-virtual";

function VirtualList({ items }: { items: Item[] }) {
  const parentRef = useRef<HTMLDivElement>(null);
  const virtualizer = useVirtualizer({
    count: items.length,
    getScrollElement: () => parentRef.current,
    estimateSize: () => 64, // 推定行高 (px)
    overscan: 5,
  });

  return (
    <div ref={parentRef} style={{ height: "600px", overflow: "auto" }}>
      <div style={{ height: `${virtualizer.getTotalSize()}px`, position: "relative" }}>
        {virtualizer.getVirtualItems().map((virtualRow) => (
          <div key={virtualRow.key} style={{
            position: "absolute", top: 0, left: 0, width: "100%",
            transform: `translateY(${virtualRow.start}px)`,
          }}>
            <ItemCard item={items[virtualRow.index]} />
          </div>
        ))}
      </div>
    </div>
  );
}

避けるべき点

  • オフセットベースのページネーション — 挿入/削除ですべてのオフセットがずれ、アイテムの重複や欠落が発生する
  • 全アイテムを読み込んでからクライアント側でフィルタリング — サーバーから現在のページのみをフェッチする
  • 1万以上のDOMノードをレンダリング — 表示行のみをレンダリングするために @tanstack/react-virtual を使用する
  • rootMargin なしのIntersectionObserver — 100〜200pxのマージンを追加して、ユーザーが下部に到達する前にプリフェッチする
  • リスト終了インジケーターの欠落 — ユーザーはこれ以上読み込むものがないことを知る必要がある

Individual skills in this repo

This repo contains 20 individual skills — each has its own dedicated page.

YepAPI/skills

WCAG 2.1 AA — semantic HTML, keyboard navigation, screen readers.

YepAPI/skills

Experiment setup, variant splitting, statistical significance, and feature flag integration.

YepAPI/skills

CRUD generators, data tables, user management, role-based access, and bulk operations.

YepAPI/skills

Monitor what ChatGPT and Gemini say about your brand using YepAPI.

YepAPI/skills

Queue workers with BullMQ/Inngest/Trigger.dev, job retries, dead letter queues, concurrency.

YepAPI/skills

Commander.js, interactive prompts with Clack, spinners, colors, config files, exit codes, and npm publishing.

YepAPI/skills

cmdk integration, Cmd+K trigger, fuzzy search, grouped actions, keyboard navigation, and dynamic action registration.

YepAPI/skills

Content planning with trend and sentiment data from YepAPI.

YepAPI/skills

Content calendar, keyword-driven planning, and content briefs.

YepAPI/skills

Theme switching with CSS variables, system preference detection, next-themes, and persistence.

YepAPI/skills

Threaded comments, @mentions, reactions, moderation queue, optimistic UI, and cursor pagination.

YepAPI/skills

Competitive analysis between domains using YepAPI.

YepAPI/skills

Layout grids, KPI cards, data tables with sorting/filtering, sidebar navigation, and responsive breakpoints.

YepAPI/skills

Data encryption — TLS everywhere, AES-256-GCM for PII, bcrypt passwords, encrypted backups.

YepAPI/skills

Chart patterns with Recharts/Nivo/D3, responsive containers, color scales, and accessibility.

YepAPI/skills

Drizzle schema definition, push vs migrate, type-safe queries, relations, transactions, and prepared statements.

YepAPI/skills

Upload handling, resize/compress, CDN delivery, blur placeholders, and next/image patterns.

YepAPI/skills

Local business and Google Maps research using YepAPI.

YepAPI/skills

Tenant isolation (row-level vs schema), subdomain routing, per-tenant config, and data partitioning.

YepAPI/skills

Chat UI components, streaming responses with AI SDK, context window management, and RAG patterns.

関連スキル