---
title: Limit pagination depth to improve query performance
description: >-
  Keep pagination depth at or below 25,000 objects and use collection filters to
  help buyers narrow results instead of browsing through deep page offsets.
source_url:
  html: >-
    https://shopify.dev/docs/storefronts/themes/best-practices/performance/limit-pagination-depth
  md: >-
    https://shopify.dev/docs/storefronts/themes/best-practices/performance/limit-pagination-depth.md
api_name: liquid
---

# 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.

***

## Why

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.

***

## How

### Better approach: use filters

Instead of deep pagination, help customers narrow results using [filters](https://shopify.dev/docs/api/liquid/objects/filter). Filtered results are much smaller and load faster than deep pagination.

```liquid
<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 %}
```

### Reasonable 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`](https://shopify.dev/docs/storefronts/themes/tools/theme-check/checks/pagination-size) check in [Theme Check](https://shopify.dev/docs/storefronts/themes/tools/theme-check), Shopify's linting tool, defaults to a `maxSize` of 250. Staying well below the maximum results in better response times.

***

## Examples

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.

### Infinite scroll pattern

For a better user experience with large collections, consider infinite scroll. Use an [`IntersectionObserver`](https://developer.mozilla.org/en-US/docs/Web/API/IntersectionObserver) on a sentinel element at the end of the grid instead of a `scroll` listener, which fires on every frame of a scroll:

```javascript
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](https://shopify.dev/docs/storefronts/themes/best-practices/performance/debounce-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.

***

## Testing

* **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.

***

## References

* [`paginate`](https://shopify.dev/docs/api/liquid/tags/paginate) tag
* [`collection.products`](https://shopify.dev/docs/api/liquid/objects/collection#collection-products)
* [`collection.filters`](https://shopify.dev/docs/api/liquid/objects/collection#collection-filters)
* [`default_pagination`](https://shopify.dev/docs/api/liquid/filters/default_pagination) filter
* [`for`](https://shopify.dev/docs/api/liquid/tags/for) tag
* [`filter`](https://shopify.dev/docs/api/liquid/objects/filter) object
* [`filter_value`](https://shopify.dev/docs/api/liquid/objects/filter_value) object
* [`filter_value.url_to_add`](https://shopify.dev/docs/api/liquid/objects/filter_value#filter_value-url_to_add)
* [The Shopify platform - Pagination limits](https://shopify.dev/docs/storefronts/themes/best-practices/performance/platform#pagination-limits)
* [PaginationSize Theme Check](https://shopify.dev/docs/storefronts/themes/tools/theme-check/checks/pagination-size)
* [Add filters to your shop](https://help.shopify.com/en/manual/online-store/search-and-discovery/filters)
* [Collection filters Liquid object](https://shopify.dev/docs/api/liquid/objects/filter)
* [Use the Section Rendering API for dynamic updates](https://shopify.dev/docs/storefronts/themes/best-practices/performance/use-section-rendering-api)
* [Limit product queries with pagination](https://shopify.dev/docs/storefronts/themes/best-practices/performance/limit-product-queries-with-pagination)
* [Debounce and throttle event handlers](https://shopify.dev/docs/storefronts/themes/best-practices/performance/debounce-throttle-event-handlers)

***
