How do you track your most viewed posts?

Hello all! I would like to display my most viewed posts on my React homepage. I have read a couple tutorials - like https://www.wpbeginner.com/wp-tutorials/how-to-track-popular-posts-by-views-in-wordpress-without-a-plugin/ - and while I can install a custom plugin just fine, I think the problem is that it doesn’t register a “view” on my post in React as a pageview. Has anyone encountered this issue before? Do you have any ideas on how to tackle this?

Hi @jeanette

Welcome to the community.

This is not going to be simple to do, but it could be done. As you’ll see from the article you refer to WordPress doesn’t do this by default and needs some custom code to register post views. You would need to do something similar in React, but you have the added complication of then sending the page view registration back to WordPress and then storing it in the meta with the post as demonstrated in the article. So it will involve both client side JS/React code and server side PHP code. In addition, as you’re sending data back to the server you will need to authenticate each Send to prevent malicious actors sending to the same endpoint. You should also take extra measures to ensure that you’re not creating any security issues as you’re writing data from client to server.

On the whole I think that this might be quite a challenging programming task. But certainly doable with enough perseverance.

Good luck. Do let us know how you get on with this.

Hi @jeanette

On second thoughts I think you could do this server side in WordPress. The solution might be largely similar to the one in the article that you cite, but hooked onto the rest_pre_echo_response hook, rather than wp_head as they do in that article. I think it could be worth experimenting with this approach.

In either case, you would also need to add the post meta to the post data returned by the WP REST API so that you have access to it from your Frontity app.

1 Like

Hi @mburridge ,

I actually looked into this, and it seems that from my quick inspection, rest_pre_echo_response hook is triggered only when Frontity runs Server-Side Rendering.

I was exploring a few options today, and came up with this one solution. It needs to be secure, too. The workflow is as follows:

Step 1.
On WordPress - create a function to create / update post meta, let’s call it post_view_count.

function my_set_post_views($post_id) {
    $count_key = 'post_view_count';
    $count = get_post_meta($post_id, $count_key, true);
    if ($count == ''){
        $count = 0;
        delete_post_meta($post_id, $count_key);
        add_post_meta($post_id, $count_key, '0');
    } else {
        $count++;
        update_post_meta($post_id, $count_key, $count);
    }
}

Don’t forget to register this metadata so that it’s accessible via REST API & on Frontity.

// Register post_view_count meta data on REST
$meta_args = array( 
    'type'                => 'number',
    'single'             => true,
    'show_in_rest' => true,
);
register_meta( 'post', 'post_view_count', $meta_args );

Step 2.
On WordPress - create a custom REST API endpoint, which can update the metadata of a given post. There’s a permission_callback here, which means you’ll need to authenticate when making the request to this endpoint (more secure).

// REST API Endpoint for Updating Post View Count
add_action( 'rest_api_init', 'post_update_endpoint' );
function post_update_endpoint() {
    register_rest_route('my/v1', '/post_update/', array(
        'methods' => 'POST',
        'callback' => 'post_update_func',
        'permission_callback' => function () {
          return current_user_can( 'edit_others_posts' );
        }
    ) );
}

function post_update_func( WP_REST_Request $request ) {
    $data = (array) $request->get_json_params();
    $id   = $data['id'];
    $update = my_set_post_views($id);
    // Optional. If the function returns anything, you can create a response here ...
    // $response = new WP_REST_Response( $update );
    // return $response;
}

Step 3.
On Frontity - server-side (server.js), create a custom endpoint that accepts post’s id as a parameter, and in turn hits WordPress REST API endpoint to trigger the metadata update.

Here’s a quick-and-dirty example on beforeSSR action, creating a custom route /update-post-view-count

import { fetch } from "frontity";

export default {
  actions: {
    theme: {
      beforeSSR: {
        const data = state.source.data[state.router.link];

        if (data.route === "/update-post-view-count/") {
          const id = data.query.id;

          if (id) {
             const { api } = libraries.source.api;
             const data = { id };
             const response = await fetch(`${api}my/v1/post_update/`, {
               method: "POST",
               headers: {
                 "Content-Type": "application/json",
                  Authorization: ``, // Use your own auth method
               },
               body: JSON.stringify(data),
             });
            
             // ctx - Koa context
             ctx.status = 200;

             // Handle response / error
             // const res = await response.json();
             // console.log(res);
          }
        }
      }
    }
  }
}

Step 4.
On Frontity - client-side post template (e.g. post.js), hit the “server-side” url. This should be run every time the post is accessed, regardless of SSR. Something like:

fetch(`/update-post-view-count/?id=${data.id}`);

Seems to be working so far. Hope that helps, but I’m very open to suggestion on how to improve the workflow :slight_smile:

2 Likes

Hi @kevin

Great work! I’m sure many people will find that solution useful.

Regarding authentication, the wp-source package now accepts a state.source.auth property. See here for more info.

Awesome, thanks @mburridge . I didn’t know about using state.source.auth before. That definitely makes the code a lot cleaner!

2 Likes