OneSheep OneSheep

Dropping in content with pjax

When we recently refactored the Toucan app it was difficult to choose the best architecture. The app uses several jQuery powered widgets to make the content more engaging and we used Vue.js to make our dashboard and reporting pages more manageable. This mixture of technologies on the front end made it difficult to build a fully integrated Single Page Application. So, to get that immersive experience while users are consuming content we decided to use the next best thing: a hybrid approach called pjax. The pjax technique has been around for a while and we have used it in projects before, but it has been made a lot easier and a lot more popular by Github’s current CEO, Chris Wanstrath. His jQuery plugin coined the term and makes the technique a lot simpler to use.

What is pjax?

pjax is a jQuery plugin that uses ajax (loading data with javascript without refreshing the page) and pushState (changing the URL of the page without refreshing the page) to deliver a fast browsing experience with real permalinks, page titles, and a working back button. It is faster because instead of loading the entire page on every click, a portion of the page is loaded and then swapped out in place of the old content. Try it yourself on this demo page.

In the client

For a server to respond correctly to a browser page request, there has to be a way to tell the server to send only a part of the page or the complete page. We can do this manually by having different URL request signatures. Or we can leverage the pjax mechanism of setting a custom HTTP request header. The plugin adds the following custom headers to the http request:


It also adds a query string to the URI:


The query string is not parsed on the server, but is added to preserve caching and proxying and stay true to the HTTP spec which says The GET method means retrieve whatever information … is identified by the Request-URI.

So, with these things added to the request for us, it tells the server that we only want partial content and specifically the part of the content that is wrapped by an id called content”

In the javascript we configure it this way:

// specify which part needs to change
var CONTENT_ID = '#content';

// now, when we want to 'turn the page', 
// we hide the existing section 
// and add a css class with a nice opacity transition

// then we call for new content
$.pjax({url: url, container: CONTENT_ID});

In another part of the code we register an event hook to watch for, and handle the server’s response like this:

$(document).on('pjax:success', function() {
  var content = $(CONTENT_ID);;
  window.setTimeout(function () {
  }, 50);

On the server

The responsibility on the server is to detect a pjax request and then send a filtered response and optionally include a x-pjax-url response header.

The pjax project page points to pjax helpers for many different server frameworks including Rails, Django, ASP​.net MVC, Grails, Express etc. Our app is served by a Laravel backend and we found a few different approaches listed there. The most natural fit was to use a pjax middleware, but there were other approaches like adding a service provider, too.

Depending on how heavily you use the feature you can use a simple middleware or a more full-featured middleware like this one that supports advanced pjax features like a version header and error response codes.

Once these two bits are in place, browsing Toucan course content feels like magic. Not only do the pages load a lot faster, but they gracefully ease in over the old content without that distracting blink of a page refresh.


The content bloc returned by the pjax response can include javascript tags to drive the new content. If however you want to also pull in an external javascript resource or library with the new page, it will not be loaded automatically. We had to wrap our page code in a $.getScript callback function:

$(function() {
    // code that doesn't need the external class 
    // can run syncronously here

    $.getScript( "/js/pager.js", function() {
        /* pager class available here */


Posted on Mar 15, 2017 by Jannie Theunissen

Back to all posts