WordPress comments, cookies and caching

This post explains how WordPress uses comment cookies and why that is detrimental to your site’s caching. It then shows you how to fix this.

When I wrote my previous post about WordPress leadership, I had anticipated getting a lot of comments. It turned out there were even more than I expected. This led to some issues with my (admittedly rather aggressive) caching settings on my blog. When I approved a comment it didn’t clear my Cloudflare edge cache of my post, so people couldn’t see them.

A few days after fixing that, I was alerted to the fact that Cloudflare was caching the details of the last commenter on a post. This led me down a rather deep rabbit hole. I was reading code that I’d not seen in quite some time, in the WordPress comment system.

Now, this is all admittedly… A bit of the stuff that makes my kids call me a boomer. Because who does all this commenting on blogs? Right, not my kids. But still, a problem nonetheless.

This is rather lengthy, so here’s a table of contents:

How the WordPress comment system works

When you leave a comment on a WordPress site, the process works as follows:

  1. Your comment is submitted;
  2. WordPress sets a cookie for each of your name, email address, and website URL;
  3. Your comment is either held for moderation or immediately approved, based on several criteria. Then WordPress redirects you to a new URL and renders the post you commented on.

The URL it redirects you to can be two things:

  • If your comment was immediately approved, it’s the URL you started on, with a #comment-<comment-id> anchor so that you’re shown your comment on the page immediately.
  • If your comment was held for moderation, by default, WordPress sends you to the URL you started on with a unapproved URL parameter + a moderation-hash parameter. It uses these to show your unapproved comment rendered where it would be rendered. It adds a message around it that your comment is held for moderation.

When WordPress renders that page, and every time after that when it renders any blog post on the site for you, your browser sends your cookies to the server, and WordPress fills the data from those cookies into the comment form. So when that view, or a later view, is proxy cached (for instance by Cloudflare), your personal data gets cached along with it.

This is of course highly undesirable on sites that have aggressive caching. On sites like that, you want every page render to be as similar to another one as possible. Even more important you certainly don’t want Personally Identifiable Information in those rendered pages.

The solution: moving comment cookies to JavaScript space

The solution to this is fairly obvious: there’s no reason why this would need to be done server side anymore, as we can do all of it in JavaScript. To do so, we have to take a few steps:

  1. Set the appropriate cookies when you leave a comment (this is going to be in JavaScript);
  2. Prevent WordPress from setting the comment cookies in the first place, as those might get cached;
  3. Prevent WordPress from adding the content of those cookies to the rendered output on the page, as that would get it cached (this will be a few simple PHP functions);
  4. Use JavaScript to read those cookies and fill the form fields.

We could do this in 3 steps if we chose not to be backwards compatible: if we chose different cookie names, we could skip step 3, but I like to keep things backwards compatible.

1. Set cookies when you comment

We’ll start by setting the comment cookies on form submission. This assumes that you’re using the default WordPress comment forms, and thus have the commentform id attribute on your comment forms.

We’ll update this code a bit later to add the functionality for step 4 as well. What this code does is fairly simple: it sets the 3 different comment cookies, with an expiry date of one year, when you submit the comment form. It sets them for / on your domain, so that they’re used on all the comment forms throughout a site.

// Function to set comment cookies.
function setCommentCookies( name, email, url ) {
    const oneYear = 365 * 24 * 60 * 60 * 1000;
    const expiryDate = new Date( Date.now() + oneYear ).toUTCString();

    // Set cookies for comment author data. This (slightly inefficient) cookie format is used for compatibility with core WordPress.
    document.cookie = `comment_author=${encodeURIComponent(name)}; expires=${expiryDate}; path=/`;
    document.cookie = `comment_author_email=${encodeURIComponent(email)}; expires=${expiryDate}; path=/`;
    document.cookie = `comment_author_url=${encodeURIComponent(url)}; expires=${expiryDate}; path=/`;
}

document.addEventListener( 'DOMContentLoaded', function () {
    const nameField  = document.getElementById( 'author' );
    const emailField = document.getElementById( 'email' );
    const urlField   = document.getElementById( 'url' );

    // Set cookies on form submission.
    const commentForm = document.getElementById( 'commentform' );
    if ( commentForm ) {
        commentForm.addEventListener( 'submit', function() {
            if ( nameField && emailField && urlField ) {
                setCommentCookies( nameField.value, emailField.value, urlField.value );
            }
        });
    }
})Code language: JavaScript (javascript)

2. Preventing WordPress from setting comment cookies

To prevent WordPress from setting comment cookies, we should just unhook the function that sets these:

remove_action( 'set_comment_cookies', 'wp_set_comment_cookies' );Code language: PHP (php)

3. Prevent WordPress from reading comment cookies

Next we want to prevent WordPress from adding the content from the comment cookies on the rendered pages. Since there is no way to prevent this functionality from running entirely, we just have to filter it to be empty:

/**
 * To prevent this data from being cached, we don't want to show it server side.
 *
 * @param array $commenter Ignored.
 *
 * @return array With empty values for all three keys.
 */
function ignore_comment_cookies_serverside( $commenter ) {
    return [
        'comment_author'       => '',
        'comment_author_email' => '',
        'comment_author_url'   => '',
    ];
}

add_filter( 'wp_get_current_commenter', 'ignore_comment_cookies_serverside' );Code language: PHP (php)

4. Fill the comment form with JavaScript

To fill the comment form with JavaScript, we have to read the values from the cookies:

// Function to read cookies.
function getCommentCookies() {
    const cookies = document.cookie.split( '; ' ).reduce(( acc, cookie ) => {
        const [key, value] = cookie.split( '=' );
        acc[key]           = decodeURIComponent(value);

        return acc;
    }, {} );

    return {
        name:  cookies['comment_author'] || '',
        email: cookies['comment_author_email'] || '',
        url:   cookies['comment_author_url'] || ''
    }
}Code language: JavaScript (javascript)

And now we update the DOMContentLoaded function we created in step 1 to add this:

document.addEventListener( 'DOMContentLoaded', function () {
    const nameField  = document.getElementById( 'author' );
    const emailField = document.getElementById( 'email' );
    const urlField   = document.getElementById( 'url' );

    // Get the data from the comments.
    const cookies = getCommentCookies();

    // Populate comment form fields with cookie data.
    if (nameField)  nameField.value  = cookies.name;
    if (emailField) emailField.value = cookies.email;
    if (urlField)   urlField.value   = cookies.url;

    // Set cookies on form submission.
    const commentForm = document.getElementById( 'commentform' );
    if ( commentForm ) {
        commentForm.addEventListener( 'submit', function() {
            if ( nameField && emailField && urlField ) {
                setCommentCookies( nameField.value, emailField.value, urlField.value );
            }
        });
    }
})Code language: JavaScript (javascript)

I have combined all of these in this gist, for easy copy pasting.

Aside: the SEO impact of the WordPress comment system

The fact that WordPress redirects to a URL with an unapproved parameter is incredibly tricky in my eyes; I’m never a fan of URL parameters being added anywhere (as it usually busts the cache, and bloats the URL space), but here it also leads to potential SEO issues if people link to these parameterized URLs. I was able to find quite a few of these with relatively simple Google search:

Since WordPress does not noindex these URLs by default, this is a perfectly sensible way of injecting links into a page and getting (usually nofollow, but still) links from domains that have no idea they’re linking to you, thinking they’ve not approved your comment.

SEO plugins should take note and add noindex to every page that has these URL parameters on it, something I think WordPress core should do too. On my site, I have decided to just not let WordPress redirect to this parameterized URL entirely, something I have included in the gist as well.

A headshot of Joost de Valk, author.

Joost de Valk

Joost is an internet entrepreneur from Wijchen, the Netherlands. He is an investor at Emilia Capital and actively working on Progress Planner. He's also the founder of Yoast. Joost is married to Marieke, who is also Yoast's former CEO. Marieke and Joost have 4 kids together.

»

WordPress » WordPress comments, cookies and caching

15 thoughts on “WordPress comments, cookies and caching”

  1. Good post. Nice to see an explanation of how full page caching can cause issues when using cookies.

    Your solution does aound like a better approach and how WordPress should be doing it.

    But I wonder how useful this functionality is actually useful for people these days? Using autocomplete is another approach which should work without cookies.

    Reply
  2. I dug into the comment system 2 years ago, and imho it is one of those areas in WP that needs some love. It feels terribly outdated compared to other areas.

    When doing a company blog, I needed to add a custom validation checkbox for consent with privacy. But there was (and still is afaik) no way to do a not hacky validation of custom fields in the comment section.

    https://core.trac.wordpress.org/ticket/57105

    So besides your suggestion, imho the comment system needs to gain some further extensibility.

    Reply
  3. > WordPress sets a cookie for each of your name, email address, and website URL

    What happens when a user posts a comment, on a site, where the user have at-least one previously approved comment, but both instances were from a new Incognito mode. Will the cookie be same?

    Reply
    • The cookies I’m talking about here are entirely dependent on the values you’ve filled in the comment forms, so yes, they might be. But as soon as you close that incognito window, those cookies get deleted, so they don’t do anything.

      Reply
  4. In a default WordPress install when writing a comment there is a checkbox “Save my name, email, and website in this browser for the next time I comment.” and that checkbox is not checked in a default installation. So default behaviour does not set a cookie for name, email, and website.

    The general solution of your described problem is to clear the cache of the post if somebody posts a comment or if a comment is approved for the post.

    No need to mess with js cookies etc.

    Reply
    • There is an option under Settings → Discussion: “Show comments cookies opt-in checkbox, allowing comment author cookies to be set” – once that is turned on, this option becomes available in comment forms. That is what I’m talkin about.

      Reply
  5. Nice idea Joost! I wish we had this feature long ago.

    The “cache rebuild” feature of WP Super Cache was contributed by a user who was annoyed at how the plugin cleared the cache when comments were made. When comments are made, it backs up the current cache file, and gives it a TTL of a few seconds. If another anon user visits the page within that time, the backed up cache file is restored and displayed while the actual page is built and cached in the background. The restored file could be shown to as many visitors as the site could handle while the cache file was being freshened. It made a huge difference on busy sites.
    That all could have been avoided by JS setting the comment details.

    WordPress might still need to set a cookie on POSTing the comment if it’s moderated. JS could then display the moderation message.

    Reply

Leave a Comment