Skip to main content

Limit pagination depth to improve query performance

Limit pagination depth to 25,000 objects or less. When pagination exceeds 25,000 items, reevaluate the user experience design to help buyers narrow results more effectively.


Deep pagination is expensive because it requires scanning through thousands of objects, increases database query time, adds server processing overhead, and can slow down other requests platform-wide. The limit applies to product collections, search results, and any Liquid array pagination. The limit also applies to count queries: counts are accurate up to 25,000 items. For arrays with more than 25,000 items, the count returns 25,001 to signal that the limit has been reached.


Anchor to Better approach: use filtersBetter approach: use filters

Instead of deep pagination, help customers narrow results using filters. Filtered results are much smaller and load faster than deep pagination.

<div class="filters">
{%- for filter in collection.filters -%}
<details>
<summary>{{ filter.label }}</summary>
{%- for value in filter.values -%}
<a href="{{ value.url_to_add }}">
{{ value.label }} ({{ value.count }})
</a>
{%- endfor -%}
</details>
{%- endfor -%}
</div>

{% paginate collection.products by 50 %}
{%- for product in collection.products -%}
{{ product.title }}
{%- endfor -%}
{{ paginate | default_pagination }}
{% endpaginate %}

Anchor to Reasonable pagination strategyReasonable pagination strategy

  • 12 to 24 products per page: A common range for good performance and user experience.
  • 50 products per page: A reasonable upper bound for most collections.
  • Maximum 500 pages: 25,000 ÷ 50 = 500 pages.
  • Encourage filtering: If customers reach page 3 or later, then suggest filters.
  • Search integration: For large catalogs, promote search over browsing.

The platform maximum for paginate is 250 items per page, and values above it are clamped to 250 rather than rejected. Metaobject entries have their own ceiling of 1,000 per page. The PaginationSize check in Theme Check, Shopify's linting tool, defaults to a maxSize of 250. Staying well below the maximum results in better response times.


For collections with thousands of products:

Instead of:

  • "Show all 30,000 products" (won't work).
  • Deep pagination through hundreds of pages (slow, poor user experience).

Do this:

  • Provide filters, such as product type, vendor, price, size, and color.
  • Show 12 to 50 products per page.
  • Help customers narrow results to a relevant subset.
  • Consider search functionality for very large catalogs.

Anchor to Infinite scroll patternInfinite scroll pattern

For a better user experience with large collections, consider infinite scroll. Use an IntersectionObserver on a sentinel element at the end of the grid instead of a scroll listener, which fires on every frame of a scroll:

let currentPage = 1;
let loading = false;

const grid = document.querySelector(".product-grid");
const sentinel = document.querySelector(".product-grid-sentinel");

const observer = new IntersectionObserver(async (entries) => {
if (!entries[0].isIntersecting || loading) return;

loading = true;
currentPage++;

try {
// Start from the current URL so active `filter.*` and `sort_by` parameters survive.
// Building the request from `pathname` alone would append products from the
// unfiltered collection into a filtered grid.
const url = new URL(window.location.href);
url.searchParams.set("page", currentPage);
url.searchParams.set("section_id", "product-grid");

const response = await fetch(url);
const html = new DOMParser().parseFromString(
await response.text(),
"text/html"
);
const products = html.querySelectorAll(".product-item");

if (products.length === 0) {
observer.disconnect();
return;
}

grid.append(...products);
} finally {
loading = false;
}
});

observer.observe(sentinel);

If you do need a scroll listener, then throttle it and register it with { passive: true }. Refer to Debounce and throttle event handlers.

Infinite scroll still respects the 25,000 limit. Provide filters to narrow results, include a Load more button as a fallback, and consider accessibility, such as keyboard navigation and screen readers.


  • Test at the limit: Try accessing page 500 or later to understand the behavior.
  • Monitor TTFB: Compare filtered and unfiltered collection load times.
  • Check counts: Verify collection.products_count for large collections.


Was this page helpful?