Home Data Center Series Cloudflare Worker + KV: Building WordPress Cloud Article Reading Statistics

1 Introduction

In WordPress, there are many ways to implement article reading statistics. The most common approach is to use a plug-in, insert code into the theme file (usually functions.php), and store the reading count in the local database. According to the implementation method, it can be roughly divided into two categories:Server statistics and Front-end statistics.

Server statistics:Generally, the number of article visits is recorded directly through PHP code, but if the website has enabled CDN Cache, many requests never actually reach the server, resulting in inaccurate statistics. For example, WordPress WP-PostViews plugin This is how it was done, and this method has gradually been phased out.

Front-end statistics:Use JavaScript to send a request directly to WordPress on the browser side, calling admin-ajax.php to record the number of visits. Although this method can bypass the interference of CDN cache, admin-ajax.php It is executed synchronously, the performance is not very high, and a large amount of traffic may slow down the overall response speed of WordPress.

In the end, neither of these two methods is ideal: I have always used WP-PostViews Plugin to count article readings, but it uses server-side statistics, and Cloudflare APO The cache mechanism of is completely conflicting, resulting in inaccurate statistics. In addition, I have been struggling with WordPress Slimming Plan, I want to minimize the use of plugins, so I decided to get rid of this useless WP-PostViews plugin.

However, the reading count function itself is still meaningful. If it is not used at all, it feels like something is missing. Therefore, the WP-PostViews plugin can be deleted, but it would be better to have an alternative solution that does not affect WordPress performance and can bypass APO cache (if it can be implemented in the cloud, it would be even better)!

actually,The demand for article reading statistics is essentially two steps:

  1. Stores the number of times an article has been read(For example, storing in a database or KV, +1 for each access).
  2. Read data from storage and display it on the page.

At this moment, I suddenly remembered that when I used Cloudflare Worker + KV to build a random image API, I had already used KV to record the number of visits per unit time when implementing the rate limit function. So if Worker can store the number of article visits in KV, wouldn’t it be a perfect solution to the storage problem? All I need to do is use JavaScript to let the browser directly request Worker, read the data from KV and display it on the page, isn’t that it?

At the same time, this solution has several obvious advantages:

• No reliance on WordPress servers: all statistical logic runs on Cloudflare In fact, WordPress is only responsible for providing the slug and does not impose additional load on the server.

• Bypass CDN cache interference: Worker handles requests directly, and statistics are recorded correctly regardless of whether the page is cached.

• Cloudflare KV has fast read and write speeds and is almost free: KV reads are usually within 10ms, and there are 1 million free requests per day, which is basically equivalent to free use of Cloudflare resources.

Thinking about it, this plan is quite interesting and worth a try!


After deleting the WordPress plugin that counts article readings, the original statistics on the page (usually an eye followed by a number) often do not disappear (the number may just be reset to zero), which is very annoying. This is because the function has been inserted into the theme file "functions.php" as code, and some plugins do not actively delete the related code when they are deleted.

According to my experience this time, I need to delete two sections of code. I record them here for reference by friends in need.

First code:

// 页面浏览量
function get_post_views(post_id){count_key = 'views';
    count = get_post_meta(post_id, count_key, true);
    if (count==''){
        delete_post_meta(post_id,count_key);
        add_post_meta(post_id,count_key, '0');
        count = '0';
    }
    return number_format_i18n(count);
}

The second code:

if (type == 'views'){
    if (function_exists('pvc_get_post_views')){views = pvc_get_post_views(get_the_ID());
    }else{
        views = get_post_views(get_the_ID());
    }
    return '<div class="post-meta-detail post-meta-detail-views">
                <i class="fa fa-eye" aria-hidden="true"></i> ' .views .
            '</div>';
}

Note 1: I didn’t expect it would be so difficult to challenge this code-based solution without any programming knowledge. I took many detours, revised the version many times, and even almost delayed the update of the article. But at least I have built the skeleton.

Note 2: Strictly speaking, I can only say that I figured it out this way, but it doesn’t mean that this is the best implementation logic, so this article can only be regarded as a sample, and I hope it can provide an idea for friends who have the same idea.

2 Solution Sorting

2.1 Overview

Before formal implementation, we first sort out the various components of the solution and the core functional points of each part. This will make the logic clearer and avoid detours in actual operation. According to the current optimization plan, the entire implementation process can be divided into three key steps:

1. WordPress gets the article slug and sends it to the Worker via JavaScript

• When the article page loads, WordPress needs to provide the article slug(ie the part of the URL that uniquely identifies the article).

• Via JavaScript Capturing slugs, and then sends to Cloudflare Worker POST request, notifying the Worker that the article has been visited once.

2. Worker processes the slug and updates the number of reads in KV

• Worker Receive slug After, first KV Find the corresponding storage item in .

• if slug exists, then the corresponding number of readings +1;if slug does not exist, then create a new Initial value is 1 storage item.

• The updated reading count will be stored back in KV to ensure data persistence.

3. WordPress obtains the article slug, queries the Worker for the number of reads through JavaScript, and finally displays it locally

• On the home page, category page, or article detail page, JavaScript needs to Send a GET request to the Worker, query The number of times the corresponding slug is read in KV.

• Worker reads the value in KV and returns Data in JSON format(Contains slug and number of reads).

• JavaScript Parsing returned data, dynamically insert the number of readings into the page so that users can see the number of visits to the article.

In this way, the entire statistical process becomes clear and efficient: the front end is responsible for collecting and displaying data, the Worker is responsible for data storage and processing, and WordPress only serves as a bridge, which greatly reduces the burden on the server and avoids the problem of CDN cache interfering with statistics in traditional solutions.

2.2 WordPress gets the article slug and sends it to the Worker via JavaScript

2.2.1 Process Overview

In WordPress,slug is a unique identifier for an article, which is usually the part of the URL that represents the article. For example, https://blog.tangwudi.com/technology/homedatacenter13164/ In this link, homedatacenter13164 is the slug of this article (of course,technology/homedatacenter13164It is also possible to use it as a slug. This slug is not only used for SEO-friendly permanent links, but is also referenced as the unique ID of the article in many plugins and functions.

The previous approach was to parse the slug from window.location.pathname directly in the front-end JavaScript code, but this approach has several potential problems: First, WordPress allows users to customize the structure of permanent links, such as usingPermalink Manager Lite This type of plugin will perform additional rewriting of the slug (/technology/homedatacenter13164/), which changes the URL rules and the result of JavaScript parsing may be wrong; secondly, if the site has Cloudflare APO enabled, the URL seen by the front end may be cached, not necessarily the actual article path, which may cause the slug obtained by JavaScript to be inaccurate.

To ensure that the slug obtained is always correct, we no longer rely on JavaScript to parse the URL, but instead Generate slug directly on the PHP side and output it to HTML. In this way, no matter how the URL structure changes, the slug can be correctly matched to the WordPress article. Specifically, we use get_permalink($post) on the WordPress server to obtain the full URL of the article, and extract the slug through the parse_url() and basename() methods. Then, this slug will be directly inserted into the HTML code for front-end JavaScript to read.

When the page is loaded, the JavaScript code will automatically read the slug inserted by PHP and send a POST request to Cloudflare Worker to pass the slug. After receiving the request, Cloudflare Worker can record the slug and store the access data in Cloudflare KV as the access statistics of the article.

The advantage of this solution is obvious. Since the slug is parsed directly in PHP, the data comes entirely from WordPress itself, ensuring correctness and not being affected by JavaScript parsing errors. Moreover, even if Cloudflare APO caches the page, JavaScript can still correctly retrieve the slug from the HTML, and the data will not be inaccurate due to caching. At the same time, this solution is also compatible with Permalink Manager Lite Such a plugin can ensure the correctness of the slug even if the URL rules are modified.

Finally, through PHP server parses slug → front-end JavaScript reads and sends request → Cloudflare Worker processes data This method successfully bypasses the cache problem while ensuring the accuracy of the data. This is not only applicable to the Cloudflare APO cache environment, but also will not be affected by WordPress plugins or URL structure changes, making the entire statistical logic more stable and reliable.

2.2.2 Practice

Insert the following code in functions.php (it is recommended to use the code snippets plugin to implement this):

add_action('wp_footer', function() {
    if (is_home() || is_archive()) { // 仅在首页或分类/标签页生效
        ?>
        <script>
        let hasFetchedViews = false; // 用于标记是否已请求过阅读次数

        window.addEventListener("load", function () {
            console.log("页面完全加载,开始执行脚本");

            // 延时执行,确保页面完全渲染
            setTimeout(function() {
                if (hasFetchedViews) return; // 如果已经请求过了,直接返回

                let articleLinks = document.querySelectorAll(".post-title"); // 直接选择 `.post-title`
                let slugs = new Set();

                if (articleLinks.length === 0) {
                    console.warn("未找到 .post-title,可能 DOM 还未完全渲染");
                    return;
                }

                articleLinks.forEach(link => {
                    let url = new URL(link.href, window.location.origin);
                    let slug = url.pathname.split('/').filter(Boolean).pop(); // 提取 slug
                    if (slug) slugs.add(slug);
                });

                if (slugs.size === 0) return;

                console.log("即将请求以下 slug 的阅读次数:", Array.from(slugs));

                slugs.forEach(slug => {
                    fetch(`/views-track?slug={encodeURIComponent(slug)}`, { // 改成 GET 请求 /views-track
                        method: "GET",
                        headers: { "Content-Type": "application/json" }
                    })
                    .then(response => response.json())
                    .then(data => {
                        if (data.success) {
                            insertViewsCount(slug, data.views);
                        } else {
                            console.error(`获取{slug} 阅读次数失败`);
                        }
                    })
                    .catch(error => console.error(`请求 {slug} 错误:`, error));
                });

                hasFetchedViews = true; // 请求完成后标记为已请求
            }, 500); // 延时 500ms,确保页面 DOM 已渲染
        });

        function insertViewsCount(slug, views) {
            let viewsText = `阅读次数:{views}`;
            let targetCards = document.querySelectorAll(`.post-title[href*="${slug}"]`); // 找到匹配的文章标题

            targetCards.forEach(card => {
                let parent = card.closest(".post-preview"); // 找到文章的最外层容器
                if (!parent) return;

                // 避免重复插入
                if (parent.querySelector(".views-count")) return;

                let viewsElement = document.createElement("div");
                viewsElement.className = "views-count"; // 便于后续查找
                viewsElement.textContent = viewsText;
                viewsElement.style.color = "#666";
                viewsElement.style.fontSize = "12px";
                viewsElement.style.marginTop = "5px";

                parent.appendChild(viewsElement);
            });
        }

        // 通过点击清除缓存并重新加载
        document.addEventListener("keydown", function(event) {
            if (event.ctrlKey && event.key === 'r') { // Ctrl + R 来清除缓存
                hasFetchedViews = false; // 清除已请求标记
                console.log("缓存已清除,准备重新加载页面统计信息...");
            }
        });
        </script>
        <?php
    }
});

The purpose of this code is: every time a user visits the article details page, this code will be triggered, and the slug of the current article will be sent to Cloudflare Worker. Then the Worker will add "1" to the number corresponding to the slug name in the KV, thereby realizing article reading statistics.

  1. Page load trigger:

• When the page is fully loaded (listened via window.addEventListener("load", …)), the statistics process starts.

  1. Delayed execution:

• Use setTimeout(function() {…}, 500) to delay execution for 500 milliseconds to ensure that the page content is fully rendered, including all DOM elements.

• After the delay, check whether the views have been requested (hasFetchedViews flag). If so, skip the execution.

  1. Get article link:

• Find all elements on the page with the class .post-title, which are links to the post titles.

• Extract the slug (unique identifier of the article) from each article link and add it to a Set, ensuring that each slug is only counted once.

  1. Send a pageview request:

• Traverse each slug and initiate a request through fetch() to obtain the number of views of the article. The requested URL is /views-track?slug=xxx.

• If the request is successful, call insertViewsCount() to display the number of views on the page.

  1. Insert View Count:

• Find the element containing the corresponding article title (post-title) based on slug.

• Insert a div element containing the number of views into the outermost container of this element (.post-preview).

  1. Cache clearing feature:

• Listen for the Ctrl + R (browser refresh) key event. When the user presses this key combination, reset the hasFetchedViews flag to false and reload the statistics.

Summarize:

• This code delays execution to ensure that the pageview request is fully loaded before initiating the pageview request, avoiding AJAX or cache interference.

• Use setTimeout and hasFetchedViews tags to ensure that each article view is requested only once to avoid duplicate requests.

• Added the function of manually clearing cache to improve the user experience.

Note: get_permalink($post) is a WordPress function used to get the permalink of a specified post, page, or custom post type. Permalink, returns the complete URL. Parameter $post can be an article ID or a WP_Post object. If omitted, the default is to get the link to the current article.


At first, I was going to use the "Custom HTTP Request" Tool to trigger statistical requests. However, after testing, it was found that for some unknown reason,"Custom HTTP REQUEST" not working properly in browser.

I suspect that the reason is that Zaraz's request mechanism is different from the fetch() request directly issued by the browser, and it may be processed or proxied by the Cloudflare server, causing the Worker to be unable to correctly identify the request source. In addition, Zaraz's request execution logic is relatively closed, and it is impossible to directly debug or view request details in the browser console, which also increases the difficulty of troubleshooting.

Therefore, the optimized solution is to have the JavaScript code on the WordPress page directly initiate the request, ensuring that each visit is counted correctly and making it easier to debug and monitor the request in the browser developer tools.


2.3 Worker processes slug and updates the number of reads in KV

2.3.1 Process Overview

Worker (can be namedviews-track) is specifically used to accept POST requests from WordPress and extract the article slug. The URL routing entry corresponding to this Worker is:blog.tangwudi.com/views-track* .

Request processing logic

After receiving the request, the Worker willDirectly operate in KV, follow these rules:

  • If the slug already exists in the KV, the corresponding reading count will be increased by 1.
  • If the slug does not exist in the KV, a new record is created and the initial value is set to 1 (i.e. starting from 0 + 1).

This has the following advantages:Simple and efficient logic, avoiding additional query operations and improving performance;No WordPress server dependency, all statistics storage and updates are done on the Cloudflare KV side;No REST API or database queries required, will not be affected by WAF restrictions or API access restrictions.

This implementation logic ensures thatRegardless of whether the article slug already exists in the KV, the number of reads can be recorded smoothly, which greatly simplifies the statistical logic while ensuring performance and scalability.


If you want to consider security, the logic can be designed to be more complex, for example: regularly synchronize the slug of WordPress articles to KV. Every time the Worker receives the slug, it first checks whether it is a valid slug that has been stored. If it exists, it will be +1, otherwise it will be discarded directly.

But this may also involveAuthentication(For example, using a signature mechanism to verify the source of the request), after tossing around for a few versions, I almost collapsed... In the end, I decidedDon't over-design, directly relying on WAF managed rules, rate limiting, and Cloudflare's automated process identification To provide basic protection.

After all, this is just aArticle reading statisticsThe data is not very sensitive, so there is no need to set up a bunch of complicated security verification mechanisms for it. The cost is totally not worth it.


2.3.2 Practice

Note: Since I have recorded the creation and configuration process of Worker in detail in several previous articles, I will not record them one by one in this article. I will only write a few key points to create Worker.

The code of Worker (views-track) is as follows:

export default {
  async fetch(request, env) {
    const url = new URL(request.url);

    // 处理 GET 请求 -> 查询阅读次数
    if (request.method === "GET" && url.pathname === "/views-track") {
      return await handleGetRequest(url, env);
    }

    // 处理 POST 请求 -> 记录并返回最新阅读次数
    if (request.method === "POST" && url.pathname === "/views-track") {
      return await handlePostRequest(request, env);
    }

    return new Response("Invalid request", { status: 405 });
  }
};

// 处理 GET 请求 -> 查询 KV 获取阅读量
async function handleGetRequest(url, env) {
  const slug = url.searchParams.get("slug");

  if (!isValidSlug(slug)) {
    return new Response("Invalid slug format", { status: 400 });
  }

  const kvKey = `views:{slug}`;
  const currentViews = parseInt(await env.views_kv.get(kvKey)) || 0;

  return new Response(JSON.stringify({ success: true, views: currentViews }), {
    headers: jsonHeaders(),
  });
}

// 处理 POST 请求 -> 记录阅读量
async function handlePostRequest(request, env) {
  try {
    const { slug } = await request.json();

    if (!isValidSlug(slug)) {
      return new Response("Invalid slug format", { status: 400 });
    }

    const kvKey = `views:{slug}`;
    const currentViews = parseInt(await env.views_kv.get(kvKey)) || 0;
    await env.views_kv.put(kvKey, (currentViews + 1).toString());

    return new Response(JSON.stringify({ success: true, views: currentViews + 1 }), {
      headers: jsonHeaders(),
    });
  } catch (error) {
    console.error("Worker Error:", error);
    return new Response("Internal Server Error", { status: 500 });
  }
}

// 校验 slug 格式
function isValidSlug(slug) {
  return slug && /^[a-z0-9-]{1,100}$/.test(slug);
}

// 统一 JSON 响应头
function jsonHeaders() {
  return {
    "Content-Type": "application/json",
    "Cache-Control": "no-store",
    "CF-Cache-Status": "BYPASS",
  };
}

You need to bind KV (you can name it at will, I use views_kv here), and its corresponding variable name is "views_kv" (the variable name is set when the Worker binds KV, so you can't use it randomly); you need to set the URL route, taking my blog as an example, its route is: "blog.tangwudi.com/views-track*", as shown below:

image.png

Note: Initially, the two functions of "write KV" and "query KV" were handled by two independent workers, one specifically responsible for receiving requests from WordPress and increasing the number of reads, and the other specifically for providing an API interface to return the number of reads of a certain article. Later, after careful consideration, there was no need to separate them. The two functions are logically complementary. Combining them in one worker can not only reduce the number of workers, simplify deployment and management, but also avoid additional overhead such as repeated parsing requests and processing URL logic. Therefore, the final solution is to let the views-track worker be responsible for receiving POST Request, add 1 to the reading count corresponding to the slug, and also handle it GET Request, query the reading volume of a slug and return the result. This merging method not only makes the code more centralized and easier to maintain, but also reduces the number of Worker triggers, thereby optimizing the overall Cloudflare computing resource consumption.

2.4 WordPress obtains the article slug, queries the Worker for the number of reads through JavaScript, and finally displays it locally

2.4.1 Process Overview

In addition to counting the number of visits, the page also needs to display the number of times the article has been read. Therefore, the front-end JavaScript code needs to send a GET request to the Worker to obtain the reading data of the current article and dynamically insert it into the page.

The specific process is as follows:

  1. When a user visits an article details page, the JavaScript code extracts the slug of the current article and then sends it tohttps://blog.tangwudi.com/views-track?slug=The slug of the current article Send a GET request.
  2. After the Worker receives the requestIt will first check whether the slug meets the format requirements, then read the number of views of the article from Cloudflare KV and return it in JSON format, such as { "success": true, "views": 123 }.
  3. JavaScript parses the returned data, get the value of views and insert it into the appropriate position of the page, such as below the article title, metadata area or other custom location.
  4. To ensure the real-time data, you can initiate a request immediately after the DOMContentLoaded event and update the reading count display on the page after the asynchronous return.

In this way, the acquisition and display of reading times are completely handled by front-end JavaScript, which will not affect the PHP operation of WordPress and will not be interfered with by CDN cache, ensuring data accuracy and efficient loading.

In addition, when showing the number of times an article has been read, there are two situations to consider:

  1. Card-style article list on the blog homepage

In the article list on the homepage or category page, each article usually displays basic information in the form of a card, such as title, summary, release date, etc. If you want to display the number of reads here, directly using JavaScript to request Workers one by one to obtain data may result in a large number of concurrent requests, affecting the loading speed. Therefore, there are several optimization solutions that can be considered:

Batch request data: If Worker supports it, the front end can request the reading counts corresponding to multiple slugs at one time, instead of requesting the data of each article separately. I use this method, as shown below:

image.png

Backend rendering cache data: You can let WordPress regularly obtain reading data through wp-cron and store it in the database, so that it can be displayed directly when the page is rendered without the need for additional front-end requests.

Local storage optimization: For articles that have already been visited, you can use localStorage to cache the number of times you have read them to reduce repeated requests.

  1. Article details page

On the article details page, it is relatively simple to obtain the reading count. All it takes is JavaScript to send a GET request to the Worker to obtain the reading data corresponding to the current slug and update the display on the page. Since the request volume is small and the real-time performance is high, the request can be executed directly when the DOMContentLoaded event is triggered, ensuring that the user can see the latest reading count when entering the article.

In general, the article details page can directly read real-time data from the Worker, while the home page or article list page needs to optimize the request method to avoid slow loading speed due to too many requests.

2.4.2 Practice

2.4.2.1 Display of the number of times the blog homepage is read

Insert the following code in functions.php (it is recommended to use the code snippets plugin to implement this):

add_action('wp_footer', function() {
    if (is_home() || is_archive()) { // 仅在首页或分类/标签页生效
        ?>
        <script>
        let hasFetchedViews = false; // 用于标记是否已请求过阅读次数

        window.addEventListener("load", function () {
            console.log("页面完全加载,开始执行脚本");

            // 延时执行,确保页面完全渲染
            setTimeout(function() {
                if (hasFetchedViews) return; // 如果已经请求过了,直接返回

                let articleLinks = document.querySelectorAll(".post-title"); // 直接选择 `.post-title`
                let slugs = new Set();

                if (articleLinks.length === 0) {
                    console.warn("未找到 .post-title,可能 DOM 还未完全渲染");
                    return;
                }

                articleLinks.forEach(link => {
                    let url = new URL(link.href, window.location.origin);
                    let slug = url.pathname.split('/').filter(Boolean).pop(); // 提取 slug
                    if (slug) slugs.add(slug);
                });

                if (slugs.size === 0) return;

                console.log("即将请求以下 slug 的阅读次数:", Array.from(slugs));

                slugs.forEach(slug => {
                    fetch(`/views-track?slug={encodeURIComponent(slug)}`, { // 改成 GET 请求 /views-track
                        method: "GET",
                        headers: {                            "Content-Type": "application/json",
                            "Cache-Control": "no-cache" // 确保请求不使用缓存
                        }
                    })
                    .then(response => response.json())
                    .then(data => {
                        if (data.success) {
                            insertViewsCount(slug, data.views);
                        } else {
                            console.error(`获取{slug} 阅读次数失败`);
                        }
                    })
                    .catch(error => console.error(`请求 {slug} 错误:`, error));
                });

                hasFetchedViews = true; // 请求完成后标记为已请求
            }, 500); // 延时 500ms,确保页面 DOM 已渲染
        });

        // 插入阅读次数的函数
        function insertViewsCount(slug, views) {
            let viewsText = `阅读次数:{views}`;
            let targetCards = document.querySelectorAll(`.post-title[href*="${slug}"]`); // 找到匹配的文章标题

            // 避免重复插入
            targetCards.forEach(card => {
                let parent = card.closest(".post-preview"); // 找到文章的最外层容器
                if (!parent) return;

                // 如果已经有 `.views-count`,则跳过
                if (parent.querySelector(".views-count")) return;

                let viewsElement = document.createElement("div");
                viewsElement.className = "views-count"; // 便于后续查找
                viewsElement.textContent = viewsText;
                viewsElement.style.color = "#666";
                viewsElement.style.fontSize = "12px";
                viewsElement.style.marginTop = "5px";

                parent.appendChild(viewsElement);
            });
        }

        // 通过点击清除缓存并重新加载
        document.addEventListener("keydown", function(event) {
            if (event.ctrlKey && event.key === 'r') { // Ctrl + R 来清除缓存
                hasFetchedViews = false; // 清除已请求标记
                console.log("缓存已清除,准备重新加载页面统计信息...");
            }
        });
        </script>
        <?php
    }
});

When displaying the number of article views on the homepage, the HTML structure of different WordPress themes may vary, especially inHow to get the article slug and query the reading data correctly in JavaScript codeIn terms of performance, it is easy to fall into traps.

The problem I encountered at first was that the JavaScript code tried to use document.querySelectorAll(".post-title a") to get the post link, but in the actual HTML structure, .post-title itself was <a> label, rather than a <div> or <h2> Container packaging <a>, resulting in document.querySelectorAll(".post-title a") directly returning an empty value.

  1. The article title structure of the theme is different:

• If the post title (.post-title) is <a> For links within the tag, use document.querySelectorAll(".post-title a") to get all article links.

• If .post-title itself is <a> Tags, directly use document.querySelectorAll(".post-title") to obtain.

It is recommended to use the browser developer tools (F12) to check the HTML structure first., making sure the selector matches the correct element.

  1. How to extract slug correctly:

• Use get_permalink() to get the full article URL, and in JavaScript you can extract the slug via new URL(url).pathname.

• If the theme does not output the slug directly on the homepage, you can output the slug to the HTML as data-slug="xxx" in functions.php through get_post_field('post_name', $post->ID), which is convenient for JavaScript to read.

  1. The way the homepage is loaded affects the display of reading times:

Lazy loading and Ajax paging: If your theme uses lazy loading or Ajax paging, new articles may appear dynamically after the initial page load. To ensure that these articles are correctly counted, you can use setTimeout to delay the request and make sure that the page elements are fully rendered before initiating the page view count request.

• In this case, you can listen to events after the page is loaded (such as DOMContentLoaded), and use setTimeout to delay execution to ensure that the page is fully rendered before triggering the statistics request.

Summarize

The HTML structure of the homepage of different WordPress themes may be different. When inserting JavaScript code to obtain the number of article readings,Be sure to check the HTML structure first to make sure the selector is correct.at the same time,The way to obtain slug may also need to be adjusted, to adapt to different theme implementations.

Final effect display:

image.png

2.4.2.2 Display of reading times on article details page

Insert the following code in functions.php (it is recommended to use the code snippets plugin to implement this):

add_action('wp_footer', function() { if (is_single()) { global post;slug = basename(parse_url(get_permalink(post), PHP_URL_PATH)); // Extract slug ?>
        document.addEventListener("DOMContentLoaded", function () {
            // 延迟 500 毫秒后执行 fetch 请求
            setTimeout(function() {
                fetch("/views-track?slug=<?php echo esc_js(slug); ?>", { method: "GET", headers: { "Content-Type": "application/json" } }) .then(response => response.json()) .then(data => { if (data.success) { console.log("article'slug); ?>' Reading Count: ", data.views); insertViewsCount(data.views); } else { console.error("Failed to get the reading count: ", data); } }) .catch(error => console.error("Request error: ", error)); }, 500); // 500 milliseconds delay in executing the request}); function insertViewsCount(views) { let viewsText = `Reading Count:{views}`; let targetElement = document.querySelector(".post-meta"); // Target insertion position (example) if (!targetElement) { console.warn("No suitable position found to insert reading times, try default insertion"); targetElement = document.querySelector(".entry-content") || document.body; } let viewsElement = document.createElement("div"); viewsElement.textContent = viewsText; viewsElement.style.color = "#666"; // Style can be modified viewsElement.style.fontSize = "14px"; viewsElement.style.marginTop = "10px"; targetElement.appendChild(viewsElement); }

Compared with the code of the blog homepage, the structure of the article details page is usually more stable, and the way to obtain the slug (get_permalink($post)) will not change significantly due to different themes.Relatively good compatibilityHowever, there are still some points to note in different WordPress themes:


1. The location where the JavaScript is inserted to count the number of views may vary from theme to theme.

let targetElement = document.querySelector(".post-meta");

• This code inserts the reading count into .post-meta (article meta information area) by default.

• butArticle details pages of different themes may not have the .post-meta class, or the class is located in a position where content cannot be inserted.

Solution:

targetElement = document.querySelector(".post-meta") || document.querySelector(".entry-content") || document.body;

.post-meta is preferred, followed by .entry-content (article body), and finally body is inserted as a backup., try to ensure that the number of readings is displayed correctly.

• You can also manually view the theme HTML structure to find the most appropriate insertion location.


2. Adaptation of article slug extraction method

$slug = basename(parse_url(get_permalink($post), PHP_URL_PATH));

In most themes, the URL structure returned by get_permalink($post) is consistent, you can directly use parse_url() + basename() to extract the slug.

• But ifUses a custom permalink structure(如 /%category%/%postname%/),那么 basename() 可能无法准确获取 slug。

Solution:

You can change it to get the post_name directly from the database to ensure the correctness of the slug:

$slug = get_post_field('post_name', $post->ID);

This methodIndependent of URL structure, adapts to all custom link formats.


3. The theme may already have a built-in reading count function

Some WordPress themes (such as Astra, Newspaper) may have built-in reading count function and display it in the article meta information.Need to avoid duplicate additionsReading times, causing UI conflicts.

Solution:

You can check in functions.php whether post-meta already has HTML tags related to the number of readings:

if (!document.querySelector(".post-meta .views-count")) { // Insert the number of views }

This avoids repeated rendering.


Summarize

• The article details page code is more compatible than the home page code, but still needsAdapt slug to extract and insert target elements of reading times, as well as the reading times statistics that come with the theme.

Insertion position: Make sure .post-meta exists, otherwise a fallback should be provided.

How to get slug: If you have a custom permanent link, it is recommended to use get_post_field('post_name', $post->ID).

Avoid double counting: If the theme already has a reading count displayed, you can check whether there is a related HTML tag in .post-meta.

The final effect is shown:

image.png

3 Conclusion

There are so many WordPress themes. The solution in this article is fine in general, but it is probably not feasible to copy it directly for different themes (except for the Argon theme). It can only be used as a reference, and it will definitely take some effort to implement it in the end.

However, as long as the toss is successful, there will be many benefits, such asCan uninstall WordPress statistics plugin, reducing the additional query burden on the server, thereby speeding up page loading and improving overall performance.Read counts are stored in cloud storage such as Cloudflare KVEven if the site is migrated, the server is changed, or even the primary and backup sites are switched, the data will not be lost, and there is no need to synchronize or migrate the local database.

In general, this solution is more suitableWebmasters who want to reduce the burden on WordPress, optimize page performance, and want to keep statistical data for a long timeHowever, when implementing it on different themes, you may need to adjust the HTML structure, CSS selectors, and slug extraction logic, and the adaptation cost varies from theme to theme.


This solution was a bit problematic at first. I found that for some regular operations (such as clicking on different article links on the site to access these articles), the reading count would not appear, but it would appear if I refreshed or reloaded the site. This phenomenon is so familiar, it feels like using AJAX,Instant.page This is caused by the "no refresh loading" function.

The core principle of this technology isIntercept the browser's default page jump behaviorThen use JavaScript to asynchronously load the content of the new page and dynamically update the DOM without actually triggering the DOMContentLoaded or load event. Although this improves access speed, it also brings a problem -Scripts are not re-executed like normal page loadsHere is the problem with my reading count solution:

• When switching pages, WordPress will not re-execute the JavaScript code in wp_footer, so the fetch request will not be sent again, and the number of readings will not be updated.

• But if you refresh the page manually, the entire wp_footer code will be reloaded and the statistics logic will work properly.

If you really want to solve this problem from a technical level, you can use MutationObserver or pjax We can use technologies such as DOM changes and manually execute statistical logic when switching pages to ensure that the number of readings can be correctly obtained and displayed without refreshing. Or, for specific no-refresh technologies (such as Instant.page), we can use the event hooks it provides to manually trigger our statistical code after each page switch. This kind of problem is actually very common when optimizing WordPress sites, such as Lazy loading, preloading, dynamic content replacement Technologies such as Optimize will bring similar side effects, so when using these optimization methods, it is also necessary to consider the compatibility of data updates and script execution.

Note 1: I thought of a compromise, usingsetTimeout(function()), directly specify to call the function to display the number of views after 500 milliseconds, so that it will not be affected by "no refresh loading". Isn't it genius? All the previous codes have been optimized with this idea.

Note 2: But there is a price to pay: I have to clear the APO cache and start over again... It's so annoying. I have to clear the cache frequently during this period. Every time I clear it, I have to re-cache it, which affects the visitor experience. The key is that it's okay to clear the HTML, but I have to clear the image cache as well. It's so painful. It seems that I have to study a way to clear only the HTML content~.

Note 3: It seems that there are still some problems, but only some problematic operation scenarios have been improved.


In addition, it is strongly recommended that you do not insert code directly into the functions.php file, but use the code snippets plug-in to manage it instead, which will be more convenient for operation and maintenance. The actual effect is as follows:

image.png

After finishing it, I used PageSpeed Insights to test it as usual. The results were good, and the highest score reached 90 points for the first time:

image.png

image.png

Note: I went through a lot of debugging in the browser developer tools, but there were too many and I am too lazy to write them down. If you need help, just ask ChatGPT how to debug using code.

分享这篇文章
The content of the blog is original. Please indicate the source when reprinting! For more blog articles, you can go toSitemapUnderstand. The RSS address of the blog is:https://blog.tangwudi.com/feed, welcome to subscribe; if necessary, you can joinTelegram GroupDiscuss the problem together.

Comments

  1. Windows Chrome 134.0.0.0
    1 month ago
    2025-3-18 9:50:24

    Code snippets? It looks like a good thing. I will definitely try it out.

    • Owner
      Autumn Wind on Weishui River
      Macintosh Chrome 134.0.0.0
      1 month ago
      2025-3-18 9:53:08

      It’s really a good thing. I really don’t want to add code to functions.php or several other theme files. It’s hard to manage and I completely forget about it after a while.

  2. Fragments
    Windows Edge 134.0.0.0
    1 month ago
    2025-3-18 9:05:43

    It’s a malpractice, I admire it.

    • Owner
      Fragments
      Macintosh Chrome 134.0.0.0
      1 month ago
      2025-3-18 9:09:56

      I messed around, I don't know programming, and I took a lot of detours~

Send Comment Edit Comment


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠(ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ°Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
Emoticons
Emoji
Little Dinosaur
flower!
Previous
Next
       
error:
en_US