Improving performance – AJAX Product Listing Pages

Web sites are moving towards becoming single web applications, offering users a richer and smoother user experience. We wanted the NET-A-PORTER website to follow a similar approach, taking advantage of the new features and APIs available in HTML5 browsers.

In general, single-page apps are characterized by their ability to refresh certain parts of the page, without causing a whole page refresh. This approach is found in thick-client web applications, where most of the processing and data storage is done on the browser side, with the backend mainly used as an API.

How we did it

We investigated two different approaches on building a single-page app:

  • Building major HTML blocks from XML

  • Including the requested HTML blocks with the response

We immediately saw performance limitations with the first approach. Constructing and injecting a small number of HTML blocks from XML wasn’t a problem, but when it came to a large number of products, the performance was clearly worse. Users noticed the delay, and the problem was more obvious on older browsers with less-optimized JavaScript engines.

Using the second approach, we only had to maintain a single template for both HTML5 browsers and HTML4 (whole-page refresh) browsers. The template was part of the response, and JavaScript injected it into the markup. This approach was considerably faster than the first one. The markup was constructed on the server, so that the only JavaScript-intensive operation on the front end was injecting the requested templates into the web page.

HTML5 APIs

HTML5 standardized a new API to manipulate the browser history and change the browser URL without causing a full-page refresh. Before the introduction of HTML5, we had to rely on techniques such as magic fragment identifiers (the part of the URL after a hash character: # ) to manipulate the browser URL. However, these fragment identifiers introduced problems that had to be handled specially, instead of being able to rely on normal browser behaviour:

  • Scroll position loss – the developer needs to use JavaScript to capture the previous scroll position when the back button is pressed.

  • Flash of content – the browser loads everything before the hash character, then JavaScript has to parse and request the parameters after the hash. This results in content appearing momentarily before the JavaScript has time to act, which is not an ideal user experience.

  • Bookmarked links – a bookmarked link with a fragment identifier might not work in a browser without JavaScript enabled.

HTML5 history.pushState provides a native solution to change the browser history and URL, rather than having the developer programmatically handle all the above problems. Moreover, most modern browsers, such as Chrome (5+), Safari(5+) and Firefox(4.0+), have very good support for the HTML5 history API.

JavaScript Page Models – Maintaining State

Modeling the View

In order to maintain the page refresh for HTML4 browsers, and progressively enhance the AJAX experience in HTML5 browsers, we had to replicate the web page state in JavaScript. We chose to have our JavaScript models as the sole source of truth for the AJAX version of the web page. That way, we would have a read-only DOM, which would be used for the HTML4 page-refresh browsers.

Here is an example of a model, representing the page state, maintaining information for total number of Pages, the total number of Products, the URL parameters and a reference to an HTML element.

    var PageModel = {
        totalPages : 0,
        totalProducts : 0,
        urlParams : {},
        'page-container' : '#page-container'
    }

Updating the View

Finally, to update the view, we had to separate the view layer from the model layer. We created update view functions, bound to the event handlers. On every user interaction, the update view function was called, and, based on our model, we updated the DOM accordingly.

That way, we managed to replicate the back-end logic in the front end using only JavaScript.
Here is an example of our update view functions updating the view layer based on the page model’s different values.

  if(PageModel.totalPages > 1){
                $(this['view-less-button']).removeClass('selected');
  }

Performance

Introducing the partial update improved not only the perceived performance, but also the actual performance, demonstrating the advantages of using HTML5 features and APIs.

We saw a great performance improvement on the product listing pages, with the average response time dropping by about 40%, since for every request we now ask for certain HTML fragments excluding major HTML blocks of the web site.

Resources – Further reading

Print Friendly

One thought on “Improving performance – AJAX Product Listing Pages

Leave a Reply