Building a “Lightweight Knowledge Index” for the Blog (Part 5): Article Series Navigation and Reading Path Design
Article Abstract
To address the issue of insufficient reading continuity in blog series articles, a structured navigation solution is implemented by building a lightweight knowledge index. Based on the article-index.json file and title rules, series identifiers and sequence numbers are extracted, allowing series article navigation cards to be dynamically generated at WordPress runtime without adding new database structures or plugin dependencies. This solution inserts a lightweight UI component containing the series name, current progress, and chapter index at the end of the article via a PHP filter, prioritizing the continuous reading path to increase user dwell time while maintaining interaction logic continuity with the existing knowledge map module, ultimately completing the structured reading experience of series content with minimal technical cost.
Qwen3-14B · 2026-06-18

1 From “Single-Article Reading” to “Continuous Reading”

A major difference between my blog and traditional personal blogs is the existence of many highly “continuous” series articles: for example, the “Cloudflare Series”, the “Awakening of Voice” series, and also the “Building a ‘Lightweight Knowledge Index’ for My Blog” series in which this article is situated.

As this type of content grows, I have gradually come to realize that for a blog, “getting users to enter an article” is not particularly difficult; the real challenge is how to make users willing to continue reading after finishing one article.

Especially since many users do not enter from the blog homepage, but rather “accidentally” land on a specific article through search engines, reposted links, or even AI search summaries. At this point, if the article itself lacks clear structural relationships and reading paths, users will often close the page directly after reading the current content, thereby achieving the “100% bounce rate” achievement.

For series articles, this is actually a great pity. Because series articles naturally possess strong contextual relationships: the previous article sets the stage, and the next article extends it. The articles are not independent “information fragments” from each other, but rather a continuously unfolding knowledge path.

However, traditional blog systems do not support this “continuous reading relationship” very well. Although WordPress itself has a “Previous / Next” function, it is mostly based on the chronological order of publication rather than true content structural relationships.

As for some off-the-shelf “series article” plugins, although they can provide features like ”series article navigation” for such articles, they usually introduce extra database structures, backend management logic, and relatively heavy plugin dependencies. This brings us back to the issue I mentioned in a previous article (see:Postscript: Why I Didn't Choose Off-the-Shelf “Related Articles” Plugins), and I don't really want my blog to become increasingly dependent on “functional plugins” to achieve these structural capabilities.

Therefore, this idea of “series article navigation” has actually always existed, but in the past, I could never find an implementation method that was lightweight and natural enough, so I put it on hold.

Until now, with the emergence of “article-index.json”, I suddenly realized that many features that previously had to rely on database queries, plugin systems, or even complex backend configurations can now be easily implemented at WordPress runtime through a lightweight index file. “Series article navigation” is a very typical example of this.

2 The Difference Between “Series Relationships” and “Semantic Relationships”

In the previous article (see:Building a “Lightweight Knowledge Index" for My Blog (Part 3): Design and Implementation of a Lightweight WordPress Recommendation System Based on Pre-computed Semantic Indexing), we have already implemented the “related articles” feature in the blog based on article-index.json.

However, when I actually started researching the “series article navigation” feature, I quickly discovered that although both it and the “related articles” feature ultimately manifest as “associations between articles,” the types of relationships behind them are actually different.

For the current “related articles” feature, it essentially still belongs to a “strong association relationship.” That is to say, these association relationships are not randomly generated, but are actively written into article-index.json after preprocessing, for example:

“14257”: {  
“title”: “xxx”,  
“related”: [  
“14244”,  
“14228”,  
“13534”  
]  
}

“title”: “xxx”,

“related”: [

“14244”,

“14228”,

“13534”

  • ]
  • }

The related here essentially belongs to a kind of "manually curated content relationship." It is more like: "After reading the current article, what content is suitable to continue reading." Therefore, although it is a type of "article relationship," it does not possess a strict sequential structure.

  • On the other hand, "series article navigation" is completely different, because "series relationships" essentially belong to a type of "structural relationship (Structure Edge)." A so-called structural relationship means: there is a clear sequential order between articles; there is a continuous reading path; and the current article has a definite position within the entire series.
  • For example: "Awakening of Voice (Part 1)", "Awakening of Voice (Part 2)", "Awakening of Voice (Part 3)". The relationship here is not "these articles are related to each other," but rather "these articles themselves are continuous nodes on the same reading path." This relationship is actually more definite than ordinary "related articles": because "related articles" are more about content extension, reading recommendations, and related topics; whereas "series relationships" are about reading order, structural positioning, and contextual continuation. The two solve different problems.
  • Because of this, I quickly realized that "series article navigation" does not actually require building a new, complex system. It requires neither additional database structures nor complex backend configurations. What is truly needed is simply: which series the current article belongs to, its current position in the series, and what other articles are in the same series.
  • And this information can actually be obtained directly based on article-index.json. Because article-index.json itself already plays the role of the "unified index layer for blog articles." As long as we can identify through title rules:
  • Series name (series_key)

Current sequence number (series_index)

Then the remaining problem actually becomes very simple:

3 Series Identification Mechanism Based on Title Rules

Traverse article-index.json

Find articles with the same series_key

Sort by sequence number

Locate the position of the current article

Get the previous and next articles

Therefore, in terms of implementation, a certain normalization process needs to be applied to the titles. Through simple regular expression rules, different forms of numbering are unified into the same numerical structure, such as mapping “one, two, three” and “1, 2, 3” to integer sequence numbers. This process is not complex, but its significance lies in allowing “human writing habits” to be stably mapped to a “system-computable structure.” After completing this layer of parsing, the next step is to truly enter the data layer processing, which is to complete global matching with the help of article-index.json.

The role of article-index.json here is not to store relationships, but to exist as a lightweight article index table. It records the basic information of all articles in the blog, including titles, URLs, and article IDs. This allows the system to traverse all articles at runtime and then execute the same series identification logic for each article.

Once the identification is complete, you only need to filter out articles with the same series_key and then sort them by series_index, and a complete series structure is naturally formed.

At this point, series identification no longer relies on any complex external systems, but is entirely completed by “title rules + lightweight index.” Its essence is actually to make the structural relationships originally hidden in writing habits explicit, and to allow the system to stably reconstruct this relationship at runtime.

After this layer is done, the subsequent steps become very straightforward: how to use this already structured series result in PHP to calculate the position of the current article and generate the navigation relationships for the previous and next articles.

4 Rendering and PHP Implementation of Series Article Navigation

On the UI, I did not design the series navigation as a complex list, but tried to keep it lightweight, retaining only two core pieces of information: the series name, current progress (e.g., 4 / 8), and the chapter index of the entire series, with a display effect similar to:

📚 系列文章:声音的觉醒(4 / 8) 
      1 2 3 4 5 6 7 8

1 2 3 4 5 6 7 8

The reason for doing this is simple: the core value of series navigation is no longer just a “path to continue reading,” but to allow readers to see the structure of the entire series at a glance, making it easy to freely choose chapters of interest while keeping the overall interface clean and the reading rhythm steady.

In terms of WordPress implementation, this part is done through the_content filter to complete the insertion. Specifically, it dynamically splices an HTML fragment during the content output process and then inserts it into the post body.

There is a key but easily overlooked detail here: the design of the insertion position order. In the current blog structure, in addition to the series navigation, there is also a “content structure prompt” module, which is used to guide users into the blog's knowledge map system:

image.png

From a purely technical perspective, both modules could simply be appended to the end of the article, but from a reading experience perspective, this order is actually not ideal.

The final design is: the series article navigation appears before the content structure prompt. That is to say, after the main text of the article, the user first sees the “continuous reading path of the current series” (the position is shown in the red box below), and then the “content structure prompt”:

image.png

This order is not arbitrary, but a very clear choice of reading priority: series navigation solves the problem of “continuing to read the same linear content,” while the structure prompt solves the problem of “jumping out of the current content system and entering the global knowledge structure.”

Providing a continuous path first while the user is still in the reading state is more in line with natural reading behavior; providing a structured entry after the user has finished reading is more like an “exit navigation supplement.”

Therefore, in terms of implementation, by controlling the_content the filter priority of, as well as the HTML splicing order, it ultimately ensures that the Series Card always appears before the content structure prompt, thereby forming a transition from “local continuity” to “global structure.”

Based on the ideas in Chapter 3, the PHP implementation is as follows (same as the previous article,article-index.json located in the theme directory:wp-content/themes/argon-theme-master/cache/article-index.json):

function extract_series_info(title) {title = trim(str_replace(' ', ' ', title));

    if (preg_match('/^(.*?)[((]([一二三四五六七八九十百零]+)[))]/u',title, matches)) {series_key = trim(matches[1]);map = [
            '一' => 1, '二' => 2, '三' => 3, '四' => 4, '五' => 5,
            '六' => 6, '七' => 7, '八' => 8, '九' => 9, '十' => 10
        ];

        index_cn =matches[2];
        index = isset(map[index_cn]) ?map[index_cn] : 0;

        if (index > 0) {
            return [
                'series_key' => series_key,
                'series_index' =>index
            ];
        }
    }

    if (preg_match('/^(.*?)(?:Part\s*|part\s*|\()(\d+)\)?/u', title,matches)) {

        return [
            'series_key' => trim(matches[1]),
            'series_index' => intval(matches[2])
        ];
    }

    return null;
}

function add_series_card_after_content(content) {

    if (!is_single() || !in_the_loop() || !is_main_query()) {
        returncontent;
    }

    if (get_post_type() !== 'post') {
        return content;
    }current_id = get_the_ID();

    json_path = get_template_directory() . '/cache/article-index.json';
    if (!file_exists(json_path)) {
        return content;
    }articles = json_decode(file_get_contents(json_path), true);
    if (!articles || !isset(articles[current_id])) {
        return content;
    }current_title = articles[current_id]['title'];
    current_series = extract_series_info(current_title);

    if (!current_series) {
        returncontent;
    }

    series_key =current_series['series_key'];

    series_articles = [];

    foreach (articles as id =>article) {

        if (empty(article['title'])) continue;series_info = extract_series_info(article['title']);
        if (!series_info) continue;

        if (series_info['series_key'] ===series_key) {

            series_articles[] = [
                'id' =>id,
                'url' => article['url'],
                'index' =>series_info['series_index']
            ];
        }
    }

    if (count(series_articles)<2) {
        returncontent;
    }

    usort(series_articles, function(a, b) {
        returna['index'] <=> b['index'];
    });total = count(series_articles);current_index = -1;

    foreach (series_articles asi => article) {
        if ((string)article['id'] === (string)current_id) {current_index = i;
            break;
        }
    }

    if (current_index === -1) {
        return content;
    }

    // =========================
    // UI
    // =========================html = '<div class="series-card">
                <div class="series-card-inner">

                    <div class="series-title">
                        📚 系列文章:' . esc_html(series_key) . '(' . (current_index + 1) . ' / ' . total . ')
                    </div>

                    <div class="series-numbers">';

    foreach (series_articles as i =>article) {

        num =i + 1;

        if (i ===current_index) {
            html .= '<span>' .num . '</span>';
        } else {
            html .= '<a href="' . esc_url(article['url']) . '">' . num . '</a>';
        }
    }html .= '</div>
                </div>
            </div>';

    return content .html;
}

add_filter('the_content', 'add_series_card_after_content', 5);

Add the following additional CSS in the active theme:

/* =========================
   Series Card 容器
========================= */
.series-card {
    display: flex;
    justify-content: center;
    margin: 14px 0;
}

.series-card-inner {
    display: inline-flex;
    flex-direction: column;
    align-items: center;

    padding: 10px 12px;
    background: #f8fafc;
    border: 1px solid #e5e7eb;
    border-radius: 8px;

    max-width: 760px; /* 关键:稳定不换行 */
}

/* =========================
   标题行
========================= */
.series-title {
    font-size: 13px;
    font-weight: 600;
    color: #111827;

    margin-bottom: 6px;
    line-height: 1.2;
    white-space: nowrap;
}

/* =========================
   数字行
========================= */
.series-numbers {
    display: flex;
    justify-content: center;
    align-items: center;
    flex-wrap: nowrap; /* 禁止换行 */
    gap: 6px;

    line-height: 1;
}

/* 普通数字 */
.series-numbers a {
    color: #0369a1;
    text-decoration: none;

    font-size: 13px;
    font-weight: 500;

    display: inline-flex;
    align-items: center;
    justify-content: center;

    width: 22px;
    height: 22px;

    border-radius: 6px;
}

/* 当前数字 */
.series-numbers span {
    color: #dc2626;
    font-size: 13px;
    font-weight: 700;

    display: inline-flex;
    align-items: center;
    justify-content: center;

    width: 22px;
    height: 22px;

    border-radius: 6px;
}

Series Card Container

image.png

========================= */

5 A Lightweight but Complete Reading Structure Patch

.series-card {

display: flex;

justify-content: center;

margin: 14px 0;

}

.series-card-inner {

display: inline-flex;

📚 系列文章:为博客构建“轻量级知识索引”(5 / 10)

View Related Categories · 3 Matches
📌 Content Structure Prompt:
This content is part of the following knowledge map, where you can view the complete content path: WordPress Learning Roadmap。
📎 Related Articles
Share this article
The blog content is original, please indicate the source when reposting! The RSS address of the blog is:https://blog.tangwudi.com/feed, welcome to subscribe; if needed, you can join theTelegram Groupto discuss questions together.

Comments

    • Tang Wudi
      ", it is recommended to disable the automatic plugin update feature on nodes other than the primary node. This is because after the primary node automatically upgrades and updates the plugins, the other nodes can actually upgrade the plugins through Syncthing's synchronization feature, although this synchronization might not be immediately perceived by local plugins (restarting WordPress on the other nodes will allow them to recognize it normally).
      Macintosh 话说才发现你居然换了配色呢
      5 months ago
      2026-6-10 10:38:53

      Chrome 148.0.0.0

Send Comment Edit Comment


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

👋 Welcome to “Wudi's Personal Blog”

Here, long-term exploration is mainly carried out around the following directions:

🧱 Personal Digital Infrastructure and Blog System Construction
☁️ Cloudflare and Network Architecture Practice
🧠 AI and Knowledge System Exploration
🛡️ Network Security and Access Optimization
🎵 Music and Sound Cognition
👁️ Cognitive Perspectives and Worldviews