# Pagination The [Storefront API uses cursors](https://shopify.dev/docs/api/usage/pagination-graphql) to paginate through lists of data and the `<Pagination />` component makes it easy to paginate data from the Storefront API. It is important for pagination state to be maintained in the URL, so that the user can navigate to a product and return back to the same scrolled position in a list. It is also important that the list state is shareable via URL. The `<Pagination>` component provides a render prop with properties to load more elements into your list. ### Example code ```jsx import {Pagination, getPaginationVariables} from '@shopify/hydrogen'; import {useLoaderData, Link} from '@remix-run/react'; export async function loader({request, context: {storefront}}) { const variables = getPaginationVariables(request, {pageBy: 8}); const data = await storefront.query(ALL_PRODUCTS_QUERY, { variables, }); return {products: data.products}; } export default function List() { const {products} = useLoaderData(); return ( <Pagination connection={products}> {({nodes, PreviousLink, NextLink}) => ( <> <PreviousLink>Previous</PreviousLink> <div> {nodes.map((product) => ( <Link key={product.id} to={`/products/${product.handle}`}> {product.title} </Link> ))} </div> <NextLink>Next</NextLink> </> )} </Pagination> ); } const ALL_PRODUCTS_QUERY = `#graphql query AllProducts( $country: CountryCode $language: LanguageCode $first: Int $last: Int $startCursor: String $endCursor: String ) @inContext(country: $country, language: $language) { products(first: $first, last: $last, before: $startCursor, after: $endCursor) { nodes { id title handle } pageInfo { hasPreviousPage hasNextPage startCursor endCursor } } } `; ``` ```tsx import {type LoaderFunctionArgs} from '@shopify/remix-oxygen'; import {Pagination, getPaginationVariables} from '@shopify/hydrogen'; import {useLoaderData, Link} from '@remix-run/react'; import {ProductConnection} from '@shopify/hydrogen/storefront-api-types'; export async function loader({ request, context: {storefront}, }: LoaderFunctionArgs) { const variables = getPaginationVariables(request, {pageBy: 8}); const data = await storefront.query<{products: ProductConnection}>( ALL_PRODUCTS_QUERY, { variables, }, ); return {products: data.products}; } export default function List() { const {products} = useLoaderData<typeof loader>(); return ( <Pagination connection={products}> {({nodes, NextLink, PreviousLink}) => ( <> <PreviousLink>Previous</PreviousLink> <div> {nodes.map((product) => ( <Link key={product.id} to={`/products/${product.handle}`}> {product.title} </Link> ))} </div> <NextLink>Next</NextLink> </> )} </Pagination> ); } const ALL_PRODUCTS_QUERY = `#graphql query AllProducts( $country: CountryCode $language: LanguageCode $first: Int $last: Int $startCursor: String $endCursor: String ) @inContext(country: $country, language: $language) { products(first: $first, last: $last, before: $startCursor, after: $endCursor) { nodes { id title handle } pageInfo { hasPreviousPage hasNextPage startCursor endCursor } } } `; ``` ## Props ### PaginationProps ### children value: `PaginationRenderProp<NodesType>` A render prop that includes pagination data and helpers. ### connection value: `Connection<NodesType>` The response from `storefront.query` for a paginated request. Make sure the query is passed pagination variables and that the query has `pageInfo` with `hasPreviousPage`, `hasNextpage`, `startCursor`, and `endCursor` defined. ### namespace value: `string` A namespace for the pagination component to avoid URL param conflicts when using multiple `Pagination` components on a single page. ### PaginationRenderProp ### contextTypes value: `ValidationMap<any> | undefined` ### defaultProps value: `Partial<P> | undefined` ### displayName value: `string | undefined` ### propTypes value: `WeakValidationMap<P> | undefined` ### Connection { nodes: Array<NodesType>; pageInfo: PageInfo; } | { edges: Array<{ node: NodesType; }>; pageInfo: PageInfo; } ## Related - [getPaginationVariables](/docs/api/hydrogen/2025-01/utilities/getpaginationvariables) ## Examples The [Storefront API uses cursors](https://shopify.dev/docs/api/usage/pagination-graphql) to paginate through lists of data and the `<Pagination />` component makes it easy to paginate data from the Storefront API. It is important for pagination state to be maintained in the URL, so that the user can navigate to a product and return back to the same scrolled position in a list. It is also important that the list state is shareable via URL. The `<Pagination>` component provides a render prop with properties to load more elements into your list. ### Multiple `Pagination` components on a single page ### Example ```jsx import {useLoaderData, Link} from '@remix-run/react'; import {getPaginationVariables, Pagination} from '@shopify/hydrogen'; export async function loader({request, context: {storefront}}) { const womensPaginationVariables = getPaginationVariables(request, { pageBy: 2, namespace: 'womens', // Specify a unique namespace for the pagination parameters }); const mensPaginationVariables = getPaginationVariables(request, { pageBy: 2, namespace: 'mens', // Specify a unique namespace for the pagination parameters }); const [womensProducts, mensProducts] = await Promise.all([ storefront.query(COLLECTION_PRODUCTS_QUERY, { variables: {...womensPaginationVariables, handle: 'women'}, }), storefront.query(COLLECTION_PRODUCTS_QUERY, { variables: {...mensPaginationVariables, handle: 'men'}, }), ]); return {womensProducts, mensProducts}; } export default function Collection() { const {womensProducts, mensProducts} = useLoaderData(); return ( <div className="collection"> <h1>Womens</h1> <Pagination connection={womensProducts?.collection?.products} // Specify a unique namespace for the pagination links namespace="womens" > {({nodes, isLoading, PreviousLink, NextLink}) => { return ( <div> <PreviousLink> {isLoading ? 'Loading...' : <span>↑ Load previous</span>} </PreviousLink> <div> {nodes.map((product) => ( <div key={product.id}> <Link to={`/products/${product.handle}`}> {product.title} </Link> </div> ))} </div> <NextLink> {isLoading ? 'Loading...' : <span>Load more ↓</span>} </NextLink> </div> ); }} </Pagination> <h1>Mens</h1> <Pagination connection={mensProducts?.collection?.products} // Specify a unique namespace for the pagination links namespace="mens" > {({nodes, isLoading, PreviousLink, NextLink}) => { return ( <div> <PreviousLink> {isLoading ? 'Loading...' : <span>↑ Load previous</span>} </PreviousLink> <div> {nodes.map((product) => ( <div key={product.id}> <Link to={`/products/${product.handle}`}> {product.title} </Link> </div> ))} </div> <NextLink> {isLoading ? 'Loading...' : <span>Load more ↓</span>} </NextLink> </div> ); }} </Pagination> </div> ); } const COLLECTION_PRODUCTS_QUERY = `#graphql query CollectionProducts( $first: Int $last: Int $startCursor: String $endCursor: String $handle: String! ) { collection(handle: $handle) { products(first: $first, last: $last, before: $startCursor, after: $endCursor) { nodes { id handle title } pageInfo { hasPreviousPage hasNextPage startCursor endCursor } } } } `; ``` ```tsx import {type LoaderFunctionArgs} from '@shopify/remix-oxygen'; import {useLoaderData, Link} from '@remix-run/react'; import {getPaginationVariables, Pagination} from '@shopify/hydrogen'; import {type Collection} from '@shopify/hydrogen-react/storefront-api-types'; export async function loader({ request, context: {storefront}, }: LoaderFunctionArgs) { const womensPaginationVariables = getPaginationVariables(request, { pageBy: 2, namespace: 'womens', // Specify a unique namespace for the pagination parameters }); const mensPaginationVariables = getPaginationVariables(request, { pageBy: 2, namespace: 'mens', // Specify a unique namespace for the pagination parameters }); const [womensProducts, mensProducts] = await Promise.all([ storefront.query<{collection: Collection}>(COLLECTION_PRODUCTS_QUERY, { variables: {...womensPaginationVariables, handle: 'women'}, }), storefront.query<{collection: Collection}>(COLLECTION_PRODUCTS_QUERY, { variables: {...mensPaginationVariables, handle: 'men'}, }), ]); return {womensProducts, mensProducts}; } export default function Collection() { const {womensProducts, mensProducts} = useLoaderData<typeof loader>(); return ( <div className="collection"> <h1>Womens</h1> <Pagination connection={womensProducts?.collection?.products} // Specify a unique namespace for the pagination links namespace="womens" > {({nodes, isLoading, PreviousLink, NextLink}) => { return ( <div> <PreviousLink> {isLoading ? 'Loading...' : <span>↑ Load previous</span>} </PreviousLink> <div> {nodes.map((product) => ( <div key={product.id}> <Link to={`/products/${product.handle}`}> {product.title} </Link> </div> ))} </div> <NextLink> {isLoading ? 'Loading...' : <span>Load more ↓</span>} </NextLink> </div> ); }} </Pagination> <h1>Mens</h1> <Pagination connection={mensProducts?.collection?.products} // Specify a unique namespace for the pagination links namespace="mens" > {({nodes, isLoading, PreviousLink, NextLink}) => { return ( <div> <PreviousLink> {isLoading ? 'Loading...' : <span>↑ Load previous</span>} </PreviousLink> <div> {nodes.map((product) => ( <div key={product.id}> <Link to={`/products/${product.handle}`}> {product.title} </Link> </div> ))} </div> <NextLink> {isLoading ? 'Loading...' : <span>Load more ↓</span>} </NextLink> </div> ); }} </Pagination> </div> ); } const COLLECTION_PRODUCTS_QUERY = `#graphql query CollectionProducts( $first: Int $last: Int $startCursor: String $endCursor: String $handle: String! ) { collection(handle: $handle) { products(first: $first, last: $last, before: $startCursor, after: $endCursor) { nodes { id handle title } pageInfo { hasPreviousPage hasNextPage startCursor endCursor } } } } ` as const; ```