How to Implement Infinite Scroll: Claude Code 활용 가이드
implement infinite scroll: Claude Code 활용. 실용적인 코드 예시와 단계별 가이드를 포함합니다.
無限스크롤の仕組み
無限스크롤は、사용자が페이지末尾に近づくと自動的に다음데이터を読み込むUIパターンです。SNSやニュース앱でおなじみですが、성능やアクセシビリティを考慮した구현は意外と難しいものです。Claude Code를 사용하면 모범 사례に沿った구현を빠르게구축할 수 있습니다。
Intersection Observer를 사용한基本구현
> Intersection Observer를 사용한無限스크롤のReactフックを作って。
> ローディング状態と에러 핸들링も含めて。
import { useEffect, useRef, useState, useCallback } from 'react';
interface UseInfiniteScrollOptions<T> {
fetchFn: (page: number) => Promise<{ data: T[]; hasMore: boolean }>;
initialPage?: number;
threshold?: number;
}
function useInfiniteScroll<T>({ fetchFn, initialPage = 1, threshold = 0.8 }: UseInfiniteScrollOptions<T>) {
const [items, setItems] = useState<T[]>([]);
const [page, setPage] = useState(initialPage);
const [loading, setLoading] = useState(false);
const [hasMore, setHasMore] = useState(true);
const [error, setError] = useState<Error | null>(null);
const observerRef = useRef<IntersectionObserver | null>(null);
const lastElementRef = useCallback(
(node: HTMLElement | null) => {
if (loading) return;
if (observerRef.current) observerRef.current.disconnect();
observerRef.current = new IntersectionObserver(
(entries) => {
if (entries[0].isIntersecting && hasMore) {
setPage((prev) => prev + 1);
}
},
{ threshold }
);
if (node) observerRef.current.observe(node);
},
[loading, hasMore, threshold]
);
useEffect(() => {
let cancelled = false;
setLoading(true);
setError(null);
fetchFn(page)
.then((result) => {
if (!cancelled) {
setItems((prev) => [...prev, ...result.data]);
setHasMore(result.hasMore);
}
})
.catch((err) => { if (!cancelled) setError(err); })
.finally(() => { if (!cancelled) setLoading(false); });
return () => { cancelled = true; };
}, [page, fetchFn]);
return { items, loading, hasMore, error, lastElementRef };
}
컴포넌트での使用例
function ArticleList() {
const fetchArticles = useCallback(async (page: number) => {
const res = await fetch(`/api/articles?page=${page}&limit=20`);
const data = await res.json();
return { data: data.articles, hasMore: data.hasMore };
}, []);
const { items, loading, hasMore, error, lastElementRef } = useInfiniteScroll({
fetchFn: fetchArticles,
});
return (
<div role="feed" aria-busy={loading} aria-label="記事一覧">
{items.map((article, index) => (
<article
key={article.id}
ref={index === items.length - 1 ? lastElementRef : null}
aria-posinset={index + 1}
aria-setsize={hasMore ? -1 : items.length}
>
<h2>{article.title}</h2>
<p>{article.summary}</p>
</article>
))}
{loading && <div aria-live="polite">読み込み中...</div>}
{error && <div role="alert">エラーが発生しました。再試行してください。</div>}
{!hasMore && <p>すべての記事を表示しました。</p>}
</div>
);
}
백엔드のカーソルベース페이지네이션
// オフセットベースよりもカーソルベースの方が성능が良い
app.get('/api/articles', async (req, res) => {
const limit = Math.min(Number(req.query.limit) || 20, 100);
const cursor = req.query.cursor as string | undefined;
const where = cursor ? { id: { gt: cursor } } : {};
const articles = await prisma.article.findMany({
where,
take: limit + 1,
orderBy: { id: 'asc' },
});
const hasMore = articles.length > limit;
const data = hasMore ? articles.slice(0, -1) : articles;
res.json({
data,
hasMore,
nextCursor: hasMore ? data[data.length - 1].id : null,
});
});
정리
Claude Code를 활용하면 Intersection Observerベースの無限스크롤からカーソルベースAPIまで一貫して구축할 수 있습니다。大量데이터の描画성능には仮想스크롤구현を、페이지네이션方式の比較は페이지네이션구현를 참고하세요.
Intersection Observer API의 상세 정보는MDN Web Docs를 확인하세요.
무료 PDF: 5분 완성 Claude Code 치트시트
이메일 주소만 등록하시면 A4 한 장짜리 치트시트 PDF를 즉시 보내드립니다.
개인정보는 엄격하게 관리하며 스팸은 보내지 않습니다.
이 글을 작성한 사람
Masa
Claude Code를 적극 활용하는 엔지니어. 10개 언어, 2,000페이지 이상의 테크 미디어 claudecode-lab.com을 운영 중.
관련 글
Claude Code용 CLAUDE.md 템플릿 7선 | 실제 프로젝트에 바로 붙여 넣는 예시
개인 앱, 콘텐츠 사이트, API, 팀 저장소, 레거시 코드베이스에 맞는 실전 CLAUDE.md 템플릿 7개와 피해야 할 실패 사례를 정리했습니다.
Claude Code Approval / Sandbox Guide | 매일 안전하게 쓰는 설정법
Claude Code의 allow, ask, deny, sandbox를 어떻게 나눌지, 실전 settings와 hooks, 실패 사례와 함께 정리합니다.
Claude Code 완벽 입문 가이드 2026 | 제로부터 실무 활용까지 7단계
Claude Code를 처음 사용하는 분들을 위한 완전 입문 가이드. 설치부터 실제 개발 워크플로우에 녹이는 것까지 — Masa가 처음에 겪었던 모든 시행착오를 바탕으로 정리했습니다.