Partial Page Caching

WP Super Cache is a full page caching plugin. It makes copies of any page that is served to visitors so the next visitor can see a pre-generated copy. That will pose a problem when a part of the page is supposed to remain dynamic on each page load, such as hit counters, ratings on posts, or shopping carts. Before you go any further, this guide will be most useful to plugin authors. If you are the user of a plugin but do not know how to code you will probably be frustrated by this guide.

There are a number of ways to tackle this problem:

  1. Use JavaScript to load the dynamic section of the website. Hit counters and rating stars are commonly done this way but whole pages are embedded by using JavaScript frameworks such as React.
  2. Don’t cache the entire page. Define the constant DONOTCACHEPAGE while the page is being created and it won’t be cached.
  3. Use dynamic caching in the plugin. This allows you to store a cached copy of the page but use a secret output tag that is replaced by the plugin with your dynamic content when the page is displayed.

Using JavaScript is outside the scope of this document, and using the constant DONOTCACHEPAGE is self-explanatory and easy to test. We will talk about dynamic caching to create a cached site that also has certain elements that refresh on page load.

Performance

Using dynamic caching is by necessity going to be slower than simply caching the page and displaying a simple html page with no PHP code. It’s impossible to tell how much slower because that will depend on how fast your site is in the first place.

How to Cache Only a Portion of a Page

  1. Enable “Enable dynamic caching” and probably “late init” on the advanced settings page and clear the cache.
  2. Add a secret string to your website where the dynamic section should appear.
  3. Use the wpsc_cachedata cacheaction to replace that secret string with the uncached content you want to show.

An example plugin has been included in WP Super Cache.

A Secret String

There are many ways to add a secret string to a page. Use wp_footer to add it to the footer of your site for example. You must keep this string secret. If you allow comments on your page then someone could add that string to their comment and the dynamic content would appear in their comment.

Late init

Much of WP Super Cache works before WordPress is fully loaded. Cached files are served before the init action fires meaning that access to the database is not possible, and you can’t use actions like wp_header or wp_footer. The “late init” setting serves the cached file after the init action has been fired. All plugins have been loaded, as has WordPress. This setting may cause cached pages to be served more slowly.

Cache actions

Since the release of this plugin WordPress has moved more code into the early part of the process before WP Super Cache is loaded, so you can now use add_filter(), apply_filters(), add_action(), and do_action(). Despite the name, the add_cacheaction() and do_cacheaction() functions work like add_filter() and apply_filters().

wpsc_cachedata

You will hook on to the wpsc_cachedata cache action and write a function that replaces the secret string defined earlier with your dynamic content. Easier said than done because your dynamic content might be the output from a complex plugin. This is why this guide is not for end users. Plugin authors will have to provide support for dynamic content in their plugins.

wpsc_cachedata_safety

This is a safety check before wpsc_cachedata is called. You must add a cacheaction function on wpsc_cachedata_safety that returns 1 if dynamic content is ready to be used.

An Example of Partial Page Caching

I want to show the current time on the server on every page load in the footer of my blog.

  1. I create a file called current_time.php and place it into wp-content/wpsc-plugins because I have set $wp_cache_plugins_dir to ABSPATH . 'wp-content/wpsc-plugins/' in my wp-config.php
  2. My secret string will be defined in a constant called CURRENT_TIME_TAG set to the string “current_time_on_my_blog_g97ag8097908y5h112”.
  3. I hook a function onto the wpsc_cachedata_safety cacheaction that returns 1.
  4. I write a function that will hook onto wp_footer that writes the CURRENT_TIME_TAG constant to the page.
  5. I write a function to hook onto wpsc_cachedata that replaces the CURRENT_TIME_TAG constant with the current time.
<?php
define( 'CURRENT_TIME_TAG', 'current_time_on_my_blog_g97ag8097908y5h112' );

function current_time_safety( $safety ) { return 1; }
add_cacheaction( 'wpsc_cachedata_safety', 'current_time_safety' );

function display_current_time_tag() {
    echo 'Current Time: ' . constant( 'CURRENT_TIME_TAG' );
}
add_action( 'wp_footer', 'display_current_time_tag' );

function show_current_time( $buffer ) {
    return str_replace( constant( 'CURRENT_TIME_TAG' ), date( 'H:i:s' ), $buffer );
}
add_cacheaction( 'wpsc_cachedata', 'show_current_time' );

Shortcodes and Output Buffers

If you need to use an output buffer to capture the output of a plugin or complicated chunk of code it gets significantly more complicated. Example two in the dynamic cache test plugin describes what goes on. Basically, you need to capture the output of your plugin during the creation of your page, before WordPress gets to the end of the page. That output is then used to replace the secret string. On cached pages you need to generate that dynamic content using an output buffer as normal.

If your plugin uses a shortcode, then that shortcode can be executed by running the function do_shortcode(). Instead of using the shortcode in your post, you would use the secret string. In the following example, we’re going to run the fictional “helloword” shortcode in the footer of your blog, capture the output from it and replace the secret string with the “hellworld” text. If the page is cached we will grab the text of the “helloworld” shortcode while the page is being served and insert it into the page.

<?php
define( 'HELLOWORLDTAG', 'hello_world_197bjagkhj7bjhasjf' );

function hello_world_safety( $safety ) { return 1; }
add_cacheaction( 'wpsc_cachedata_safety', 'hello_world_safety' );

function get_hellworld_shortcode() {
    ob_start();
    do_shortcode( '[helloworld]' );
    $text = ob_get_contents();
    ob_end_clean();
    define( 'HELLOWORLDTEXT', $text );
}

// generate the helloworld text in the footer of your uncached page.
add_action( 'wp_footer', 'get_helloworld_shortcode' );

// when a newly cached page (or old one) is about to be served replace our secret string with the helloworld shortcode.
function helloworld_dynamic_content( $buffer ) {
    if ( ! defined( 'HELLOWORLDTEXT' ) ) {
        get_helloworld_shortcode();
    }
    return str_replace( contstant( 'HELLOWORLDTAG' ), constant( 'HELLOWORLDTEXT' ), $buffer );
}
add_cacheaction( 'wpsc_cachedata', 'helloworld_dynamic_content' );