Boost time-on-site by auto-loading new posts like qz.com and Medium

Boost time-on-site by auto-loading new posts like qz.com and Medium

Are you finding that despite your considerable efforts to drive traffic to your WordPress site, the bounce rate and time-on-site stubbornly refuse to improve as your visitors read that one page then move on?

Take a leaf out of the books of mega sites qz.com and Medium and get proactive. Don’t wait for the reader to click for a new post, put it right in front of them. Automatically. And give them every reason to stay a little longer.

Let me show you how.

Photo of a quote that says: the more time you spend with anybody, the more interesting they get
We all want our visitors to think we’re interesting, don’t we?

The battle for readers’ attention has never been more fierce and once they visit your site, you want them to stay for as long as possible, engaging with the content, engaging with you and, perhaps, engaging with any revenue-generating mechanisms.

Yet, despite your good traffic levels, your bounce rate (single page visits) and average duration of visit stats stubbornly refuse to improve.

The biggest problem is that readers rarely come to your site by the “front door” any more. They follow links from search engines, social media sites and email newsletters which means that they are entering your site at a myriad of URLs.

Now, every post is a landing page but the templates for posts are rarely optimized to encourage further exploration of your site.

The standard mechanism is the incredibly low-key “previous” and “next” post navigation. You find might some improvement by using tag clouds or related post plugins but these approaches all have a fundamental flaw:  they require action by the reader, who has to click on the link.

And the only driver is the title of the post.

Take Out The Middle-Man

Screenshot of qz.com showing the title of the auto-loaded next post
The headline of the next post is already visible – as is the in-between ad

Why not bypass the need for the reader to decide to click on the link and simply just load up the next post when they scroll close to the end of the current post? An “infinite” scroll for the post view.

If you need convincing of the impact of such an approach, visit Atlantic Media’s Quartz magazine (qz.com). Start reading a post and when you get to the bottom of the original post you are already presented with the next post.

No relying on a catchy title to generate a click just the continuation of the action you’ve already been taking to get the bottom of the current article: scrolling.

Medium takes a similar approach but instead of immediately showing the next post, it shows a much fancier link and then displays the post on the click. This happens instantly, with a nice animation effect, as the post has been pre-loaded.

Of the two, I prefer Quartz’s approach with its removal of the need for reader action (other than a scroll) for the next post to be made visible. It feels more intuitive and more effective and so that’s what we are going to look at adding to your site.

Screenshot of the next post teaser on the Medium website
Medium’s “next” post link sure beats the WordPress default text link

The 30,000 Feet View

The good news is that adding automatic post loading to your site is fairly straight-forward. The bad news is that because it is reliant on the theme you are using, it can’t be achieved with just a plugin.

Here’s an overview of how the solution works:

  1. Each post is preceded with an HR tag that contains specific attributes with the title and the post URL. Javascript in the browser watches for the exit and entry of these HR elements.
  2. When they enter, the browser catches this, looks for the post navigation grabs the post URL (usually previous), modifies it and makes an ajax call to WordPress. This means that the loading of next post is performed early, hopefully making for smooth scrolling.
  3. WordPress returns the HTML for just the post (so no header, footer, sidebar, etc)
  4. The new HR and the new post HTML is added beneath the current post (and the post navigation is removed)
  5. When the article gets to the top of the browser window, the location bar is updated with the URL of the new post (along with the document title) – good for enabling sharing and bookmarking.

I’m  going to walk you through implementing this on the current WordPress default theme, Twenty Fourteen.

We are going to combine a simple plugin which will house the generic logic with a child theme that will contain the theme dependent files.

Chances are that you are not using Twenty Fourteen but I would recommend testing this solution on that theme first, just to get the hang of it, before porting it to another theme.

Creating The Plugin

The plugin performs the following tasks;

  1. Adding links to appropriate javascript files when displaying a post page
  2. Create a new endpoint for returning the HTML for just the requested post

Adding The Javascript Files

Our solution makes use of a couple of third-party libraries: Scrollspy for catching when the navigation links come into view; and History.js which allows us to change the URL in the browser location without causing a reload.

In the plugin, we make use of the wp_enqueue_scripts action to include the scripts but only if the page being displayed is a post:

1
2
3
4
5
6
7
8
9
10
11
function alnp_enqueue_scripts() {

wp_enqueue_script( 'scrollspy', plugins_url() . '/autoloadpost/js/scrollspy.js', array('jquery'), null, true );
wp_enqueue_script( 'history' , plugins_url() . '/autoloadpost/js/jquery.history.js', array('jquery'), null, true );
wp_enqueue_script( 'autoloadpost', get_stylesheet_directory_uri() . '/js/autoloadpost.js', array('scrollspy'), null, true );

}

if ( is_singular() ) {
add_action( 'wp_enqueue_scripts', 'alnp_enqueue_scripts', 10 );
}

The third javascript file, autloadpost.js, contains the main client-side logic for the solution and is theme-dependent, so it is served from the (child) theme folder. The third-party files are constant and so are served from the plugin library.

Creating The New Endpoint

Our solution is based on adding a new post to the current post page and obviously needs the HTML for the new post to do so.

To achieve this, the plugin is going to make a new endpoint available, partial, which we can intercept and process accordingly.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
function alnp_add_endpoint() {
add_rewrite_endpoint( 'partial', EP_PERMALINK );
}

add_action( 'init', 'alnp_add_endpoint' );

function partial_endpoints_activate() {

// ensure our endpoint is added before flushing rewrite rules
alnp_add_endpoint();

// flush rewrite rules - only do this on activation as anything more frequent is bad!
flush_rewrite_rules();
}

register_activation_hook( __FILE__, 'partial_endpoints_activate' );

function partial_endpoints_deactivate() {
// flush rules on deactivate as well so they're not left hanging around uselessly
flush_rewrite_rules();
}

register_deactivation_hook( __FILE__, 'partial_endpoints_deactivate' );

This code simply makes WordPress aware of the new endpoint whilst the flush on (de)activation just keeps everything nice and tidy. If we deactivate the plugin, we don’t want the endpoint to remain active.

This by itself doesn’t do much. What we need to do is detect when the new endpoint is being used and generate the post-only HTML. We can do that by using the template_redirect action:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function alnp_template_redirect() {
global $wp_query;

// if this is not a request for partial or a singular object then bail
if ( ! isset( $wp_query->query_vars['partial'] ) || ! is_singular() )
return;

// include custom template
include get_stylesheet_directory() . '/content-partial.php';

exit;
}

add_action( 'template_redirect', 'alnp_template_redirect' );

This gets called as WordPress is working out which template to use to satisfy the request. We simply add a check to see if the partial endpoint has been used and that the request relates to a post. If both are true then the post HTML is generated using a template from the child theme library (we’ll build this a little later).

The exit; command ensures that no additional templating takes place and we get just the HTML we want.

Much of this is taken from an old but still relevant and excellent article on the Rewrite Endpoint API by Jon Cave.

Creating The Child Theme

Obviously, it is preferable to wrap up the whole solution in a plugin but given the infinite possibilities for the formatting of posts by themes, this isn’t possible.

As I mentioned in the intro, I’ve built this solution to work with Twenty Fourteen, so if you want to use it with a different theme then you’ll almost certainly need to change:

1. autoloadpost.js – contains all the main logic for setting up Scrollspy, getting the new post HTML and inserting it into the page, handling the changing of the URL in the browser location

2. content-partial.php – handles the generation of the HTML for the partial call on a post

Keeping these files in a child theme, rather than editing them in the plugin folder, is not only cleaner but also makes it easier to swap themes in and out without having to touch the plugin.

Critical Dependency: Post Navigation Links

The entire solution depends on the presence of the post navigation links as they provide the URL for the new post.

If you are not using Twenty Fourteen, you should check the post template in your theme to ensure that it does actually output the post navigation links and that the links have rel attributes of next and prev. For example, here are the navigation links, in full, from Twenty Fourteen:

1
2
3
4
5
6
7
8
<nav class="navigation post-navigation" role="navigation">
<h1 class="screen-reader-text">Post navigation</h1>
<div class="nav-links">
<a href="http://twentyfourteendemo.wordpress.com/2012/02/13/this-is-it-for-now-see-you-all-next-summer/" rel="prev" sl-processed="1">
<span class="meta-nav">Previous Post</span>This is it for now! See you all next summer!</a>
<a href="http://twentyfourteendemo.wordpress.com/2012/02/13/standard-post-with-featured-image/" rel="next" sl-processed="1">
<span class="meta-nav">Next Post</span>Standard post with featured image</a> </div><!-- .nav-links -->
</nav>

If your theme’s post template doesn’t generate the navigation links with the rel attribute or at all, then you’ll need to copy the post template to your child theme and update it to do so.

Localizing autoloadpost.js

There are a number of changes that you might need to make to this file to make it work with your theme:

1
2
3
4
var comments_container = 'div#comments';
var content_container = 'div#content';
var nav_container = 'nav.post-navigation';
var post_title_selector = 'h1.entry-title';

These containers are all referenced elsewhere in the script, so I’ve globalized them to make it easier to change. You’ll either need to look through your theme or through the source code of a post page and find equivalents.

1
2
3
4
5
6
7
8
9
10
jQuery(document).ready(function() {

// don't do this if looking for comments
if (window.location.href.indexOf( '#comments' ) > -1) return;

jQuery(comments_container).hide();

...

});

In Twenty Fourteen, there is a comment link in the author meta and if this has been clicked (and therefore there’s #comment at the end of the URL) we don’t want to hide the comments or, indeed, execute any auto-loading. You can remove this line if your theme has no such link.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
function doAutoLoad(){

// grab the url for the new post
var post_url = jQuery('a[rel="prev"]').attr('href');

if ( !post_url ) return;

// check to see if pretty permalinks, if not then add partial=1
if ( post_url.indexOf( '?p=' ) > -1 ) {
np_url = post_url + '&partial=1'
} else {
np_url = post_url + '/partial';
}

// remove the post navigation HTML
jQuery(nav_container).remove();

jQuery.get( np_url , function( data ) {

var $post_html = jQuery( '<hr class="post-divider" data-url="' + post_url + '"/>' + data );

var $title = $post_html.find( 'h1.entry-title' );

jQuery( content_container ).append( $post_html );

// get the HR element and add the data-title
jQuery('hr[data-url="' + post_url + '"]').attr( 'data-title' , $title.text() );

// need to set up ScrollSpy on new content
initialise_Scrollspy();

});

}

This is the function that actually performs the automatic loading of the new post.

The function first grabs the post navigation element (as we delete them as they are processed, there should only be one in the document); the URL of the next post is contained in the href attribute.

The endpoint is added (/partial for pretty permalinks, &partial=1 for plain), the post navigation links are removed from the HTML and an ajax call is made using the modified URL.

A divider (HR) is added to the response (post HTML) and this is added to the bottom of the content container, effectively adding the post immediately after the current post. The data-title attribute is then added to the HR element.

You shouldn’t need to change this function.

Localizing content-partial.php

This file is responsible for generating the post-only HTML that is sent back to the browser when the partial endpoint is used.

The easiest way to localize this file is to find your theme’s single.php and copy The Loop code. Taking this approach will ensure that post formats are honored.

For example, here’s the single.php for Twenty Fourteen:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
<?php
/**
* The Template for displaying all single posts
*
* @package WordPress
* @subpackage Twenty_Fourteen
* @since Twenty Fourteen 1.0
*/

get_header(); ?>

<div id="primary" class="content-area">
<div id="content" class="site-content" role="main">
<?php
// Start the Loop.
while ( have_posts() ) : the_post();

/*
* Include the post format-specific template for the content. If you want to
* use this in a child theme, then include a file called called content-___.php
* (where ___ is the post format) and that will be used instead.
*/
get_template_part( 'content', get_post_format() );

// Previous/next post navigation.
twentyfourteen_post_nav();

// If comments are open or we have at least one comment, load up the comment template.
if ( comments_open() || get_comments_number() ) {
comments_template();
}
endwhile;
?>
</div><!-- #content -->
</div><!-- #primary -->

<?php
get_sidebar( 'content' );
get_sidebar();
get_footer();

As we only want the actual post HTML, we don’t need the calls to get_header, get_sidebar or get_footer nor do we need the two container DIVs.

So our content-partial template is just The Loop:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?php
// Start the Loop.
while ( have_posts() ) : the_post();

/*
* Include the post format-specific template for the content. If you want to
* use this in a child theme, then include a file called called content-___.php
* (where ___ is the post format) and that will be used instead.
*/
get_template_part( 'content', get_post_format() );

// Previous/next post navigation.
twentyfourteen_post_nav();

endwhile;
?>

All WordPress themes will use The Loop, so it should be relatively straight-forward to create your content-partial. The other option is simply to copy the theme’s content.php file but this will mean that post formats will not be honored.

Changing History – How The URL Updating Works

One of the neat aspects about this solution is that it keeps the URL in the browser location synchronized with the currently displayed post, so effectively as you scroll forwards and backwards, the URL will change as the post changes.

Every post, including the original, is preceded by an HR element with a class of post-divider and a custom attribute, data-url, which is set to the post’s permalink.

Scrollspy is used to trigger an event when an HR element either enters or leaves the viewing window:

1
2
3
4
// spy on post-divider - changes the URL in browser location and autoloads next post
jQuery('.post-divider').on('scrollSpy:exit', changeURL );
jQuery('.post-divider').on('scrollSpy:enter', changeURL );
jQuery('.post-divider').scrollSpy();

The changeURL function is the same for both exits and entries and checks to see if the movement is at the top of the browser window. An entry or exit at the top means that the post title is at the top and that we need to change the URL. Adding the HR for the original post means that we can still set the original URL.

The changeURL function will also change the document title using the data-title attribute and calls the doAutoLoad to perform and necessary post loading.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
function changeURL(){

var el = jQuery(this);
var this_url = el.attr('data-url');
var this_title = el.attr('data-title');
var offset = el.offset();
var scrollTop = jQuery(document).scrollTop();

// if exiting or entering from top, change URL
if ( ( offset.top - scrollTop ) < 150 ) {
curr_url = this_url;
History.pushState(null, null, this_url );
window.document.title = this_title;
}

doAutoLoad();

}

There's also a check when the URL changes to see if it matches the current URL. They will only be different if the browser back button has been used, so in this case, we simply reload the page with the new URL.

{code}

// Bind to StateChange Event
History.Adapter.bind(window,'statechange',function(){ // Note: We are using statechange instead of popstate

var State = History.getState(); // Note: We are using History.getState() instead of event.state

if (State.url != curr_url) {
window.location.reload(State.url);
}

});

Further Considerations

Keeping Google Analytics On Track

One of the disadvantages with automatically loading up new posts is that potentially you’ll miss tracking that page view in Google Analytics as the load does not cause a page refresh.

You can mitigate this by explicitly calling GA in the doAutoLoad function after the call to the History function:

1
2
3
4
5
6
7
8
9
10
// update the browser location
History.pushState(null, null, post_url);

// if you use async GA
_gaq.push(['_setAccount', 'UA-12345-1']);
_gaq.push(['_trackPageview', post_url])

// if you use traditional GA
var pageTracker = _gat._getTracker("UA-12345-1");
pageTracker._trackPageview( post_url );

Inserting Advertising

A potential disadvantage of ajax-based post loading for sites that contain advertising, is the lack of a page refresh means that banner ads also remain unchanged.

Again, we can take a leaf out of qz.com’s book and use “between-post” advertisements. Not only does this keep the advertisements turning over but potentially gives them more exposure as the reader has to work out whether they are an ad or the next post.

To make use of “between-post” advertisements, simply include your ad activation code at the bottom of the content-partial.php template. Shortcodes will be fine as all the usual processing of the post template will still take place.

Implement and Track

This is a relatively simple solution that potentially has huge benefits as it massively reduces the effort involved in moving onto the next article. In fact, it almost feels like a stream of content rather than individual posts.

Of course, ensuring that each and every post on your site is interesting and engaging certainly won’t hurt, either.

In these days of single entry visits, you need to do something to boost engagement and mimicing the good ideas from other sites is always a good course of action.

Automatically loading the next post works well for qz.com (be interesting to see if Medium fully follows suit) and is definitely worth considering for sites of any size.

Download: twentyfourteen_child.zip (theme) autoloadpost.zip (plugin)

Photo credit: Lisa Brewster

If you do implement automatic post loading then I’d love to know what impact it has on your visitors average visit duration. Alternatively, what do you think of this tactic for improving engagement and have any others caught your eye?