Three ways to serve long lists โ and the UX honest-truths product teams learn the hard way.
The UX decision first
- Pagination: users need to FIND and RETURN to items (search results, tables, admin) โ position is shareable and stable
- Infinite scroll: passive browsing feeds (social, galleries) โ engagement up, findability gone, footer unreachable
- Load-more button: the honest middle โ user stays in control, footer stays reachable. Default to this
Load-more / infinite core
let page = 1, loading = false, done = false;
async function loadPage() {
if (loading || done) return;
loading = true;
const res = await fetch(`/api/posts?page=${page}&limit=20`);
const items = await res.json();
if (items.length < 20) done = true; // last page detection
grid.insertAdjacentHTML("beforeend", items.map(cardHTML).join(""));
page++; loading = false;
}
// load-more: btn.onclick = loadPage
// infinite: observe a sentinel div after the grid
new IntersectionObserver(([e]) => e.isIntersecting && loadPage(),
{ rootMargin: "600px" }).observe(sentinel);Pagination URL rule
Page state belongs in the URL (?page=3) so refresh and share work โ the URL API guide covers the pattern. SEO note: Google indexes paginated links; it struggles with pure infinite scroll โ another reason content sites paginate.