One common implementation of LiftIgniter is to populate an infinite scroll of recommended content. You can also use the method described below for both an infinite scroll or for actually paginated recommendations, where the user can jump to a specific set of items by clicking on the page number. 

In an infinite scroll scenario, imagine that each set of recommendations that is returned becomes it's own page. While the dividing line between the end of one set and loading the next may be invisible to the end user, our models must be able to keep track of which set of items is being viewed, and which item of that set was clicked on, in order to correctly optimize and personalize future recommendations.

When powering a paginated set of results using a LiftIgniter query, we therefore need to make sure of the following:

  • Tracking Paginated Queries: Data sent to LiftIgniter correctly records what page of recommendations was shown, and only includes items visible on that page.
  • Consistency (may not apply in some contexts): If the user has already visited a page and then revisits that page, the same set of results is shown.
  • Deduplication: Different pages should not show the same items again. In other words, the infinite scroll should continuously populate with new items

Tracking Paginated Queries

You need to call our tracking function for each "page" of recommendations, and ensure they are tracked separately.

  • If your different recommendation pages are part of the same DOM (as would occur in the case of an infinite scroll of recommendations) you need to make sure that the selector you use to define your elements when tracking only matches the recommendations in that page.
  • In order for our system to correctly identify what numbered page it is, you should include opts: {page: "page number"} in your $p("track",...) function.

Here's what your the $p("track") portion of your widget code will look like (with changes indicated)

$p('track', {
    opts: {page: 2}, // the page number should increment with each new set of recommendations requested. This helps us separately track the different pages despite them having the same widget name
    elements: document.querySelectorAll('selector that matches ONLY the recommendations for the set of items returned for this request'), // This is often done by dynamically incrementing the id of the HTML element. Be especially careful about colliding selectors in the case that all the recommendation pages are in the same DOM, e.g., in an infinite scroll or feed of recommendations
    name: 'your widget name', // widget name should be the same across the different pages
    source: 'LI' // or 'base' if it's the control variant in an A|B test


In some cases, you may want to have consistency for paginated recommendations: if a user returns to a page in the recommendation area that they have already seen, then the user should see the same recommendations that were there previously. For an infinite scroll or single-page app, where you are storing the already-visited recommendations in the DOM, consistency is automatic.

In other cases, where the DOM is refreshed or replaced as the user moves through the recommendations, you would need to store the recommendations rendered previously in the client's local storage. If the end user revisits a page, then the recommendations get pulled from local storage, thereby guaranteeing consistency.


In most cases, you'd want to make sure that the recommendations shown on different pages, or at different points in the infinite scroll, are different from each other.

There are two ways of doing the deduplication:

  • Implicit deduplication (handled by LiftIgniter): LiftIgniter automatically deduplicates recommendations across pages using a pageviewId field that is automatically packaged in by our JavaScipt SDK. However, this deduplication happens only between recommendations returned by LiftIgniter that are shown on the page. We must get a widget_shown event in order to deduplicate. It may not work correctly if:
    •  the user navigates between multiple URLs of the site and then returns to an earlier URL (as we keep server-side records for only one current page/URL). If you are doing an API integration, you must explicitly include a value for the pageviewId across all your widget requests so that we know each request is for the same current page and that the user has not moved off the current URL.
    • A widget_shown event is not received - our models will interpret this as though the items were not loaded on the page, and are therefore still valid recommendations
  • Explicit deduplication: You can send a list of items to exclude via the "excludeItems" parameter in the "opts" setting of the $p("register") call, like this:
$p('register', {
                  max: 100, // Large number requested so you can see our full range of items
                  opts: {excludeItems : ["url1", "url2"]},
                  widget: 'default-widget', // name of widget
                  callback: function(resp) {
                    console.log(JSON.stringify(resp, null, 2));
                    console.log("// TEST.");