Contents
- 1. From "Single-article Reading" to "Continuous Reading"“
- 2. The difference between "series relationship" and "semantic relationship"
- 3. Serial Identification Mechanism Based on Title Rules
- Rendering of the 4-Series Article Navigation and its PHP Implementation
- 5. A lightweight yet complete reading structure patch
1. From "Single-article Reading" to "Continuous Reading"“
One major difference between my blog and traditional personal blogs is that it contains many highly "continuous" series of articles: such as the "Cloudflare series," the "Awakening of the Voice" series, and the "Building a 'Lightweight Knowledge Index' for the Blog" series, which is the subject of this article.
As this type of content increases, I've gradually come to realize that for blogs, "getting users to click on an article" isn't particularly difficult. The real challenge is getting users to continue reading after finishing an article.
In particular, many users do not enter the blog from the homepage, but rather through search engines, reposted links, or even AI-generated search summaries, "accidentally" stumbling upon an article. In this case, if the article itself lacks a clear structural relationship and reading path, users will often close the page directly after reading the current content, thus achieving a "bounce rate of 100% TP4T".
This is actually a pity for a series of articles. Because a series of articles naturally has a strong contextual relationship: the previous article lays the groundwork, and the next article extends the discussion. The articles are not independent "fragments of information" but rather a continuous unfolding path of knowledge.
However, traditional blogging systems don't really support this kind of "sequential reading relationship" very well. Although WordPress itself has a "previous post/next post" function, it is more based on the order of publication time than on the actual content structure.
As for some ready-made "series article" plugins, while they can provide functions similar to "series article navigation" for such articles, they usually introduce additional data structures, backend management logic, and relatively heavy plugin dependencies. This brings us back to the problem I mentioned in a previous article (see:Postscript: Why I didn't choose the readily available "Related Articles" pluginI don't really want my blog to rely more and more on "functional plugins" to accomplish these structural capabilities.
So, the idea of this "series of articles navigation" has always existed, but I have never found a lightweight and natural way to implement it, so I put it aside.
Until now, with the advent of "article-index.json", I suddenly realized that many functions that used to require database queries, plugin systems, or even complex backend configurations can now be easily implemented in WordPress runtime using a lightweight index file, and "article series navigation" is a very typical example of this.
2. The difference between "series relationship" and "semantic relationship"
In the previous article (see:Building a Lightweight Knowledge Index for Blogs (Part 3): Design and Implementation of a Lightweight Recommendation System for WordPress Based on Pre-computed Semantic IndexesWe have already implemented the "Related Articles" feature in the blog based on article-index.json.
However, when I actually started to study the "Series Article Navigation" function, I quickly discovered that although it and the "Related Articles" function both ultimately show that "there is a connection between the articles", the types of relationships behind them are actually different.
The current "Related Articles" feature is essentially still a form of "strong association." That is, these associations are not randomly generated, but rather pre-processed and actively written into the `article-index.json` file. For example:
“"14257": { "title": "xxx", "related": [ "14244", "14228", "13534" ] }
The "related" relationship here is essentially a "manually selected content relationship," more like "what content is suitable to continue reading after reading the current article." Therefore, although it is a type of "article relationship," it does not inherently possess a strict sequential structure.
"Series article navigation," however, is entirely different because "series relationships" are essentially a type of "structural relationship." A structural relationship refers to: a clear sequential order between articles; a continuous reading path; and a definite position of the current article within the entire series.
For example, in the titles "The Awakening of the Voice (Part 1)," "The Awakening of the Voice (Part 2)," and "The Awakening of the Voice (Part 3)," the relationship is not that "these articles are related to each other," but rather that "these articles are consecutive nodes on the same reading path." This relationship is actually more definite than that of ordinary "related articles," because "related articles" are more about content extension, reading recommendations, and related themes; while "series relationships" are about reading order, structural positioning, and contextual continuity. The two address different issues.
That's why I quickly realized that a "series article navigation" didn't actually need to be built from scratch. It required neither an additional database structure nor complex backend configuration. What it truly needed was simply: which series the current article belonged to, its position within the series, and which other articles were in the same series.
This information can actually be entirely generated based on article-index.json, as it already serves as a unified index layer for blog posts. As long as the title rules can be used to identify...
- Series name (series_key)
- Current sequence number (series_index)
Then the remaining question becomes very simple:
- Iterate through article-index.json
- Find articles with the same series_key
- Sort by serial number
- Locate the current article position
- Get the previous and next articles
The entire process no longer requires complex database queries.
In other words, while "Series Article Navigation" may seem like a new feature, it is essentially just a natural extension of the lightweight knowledge indexing capabilities of article-index.json.
3. Serial Identification Mechanism Based on Title Rules
After clarifying the difference between "series relationship" and "semantic relationship", the next problem to be solved becomes very specific: how exactly does the system determine that an article belongs to a certain series?
Without introducing a plugin system or an additional database structure, there aren't many options available. Ultimately, my chosen approach was quite direct—extracting structural information from the article title itself.
The reason is quite simple: in actual writing, series of articles often naturally have very obvious naming patterns, such as "The Awakening of the Voice (I) (II) (III)". These titles are not randomly named, but already express an implicit structural relationship, only this relationship has not yet been systematically utilized. From this perspective, the so-called "series identification" is essentially not about "creating relationships", but about "identifying existing relationships".
Therefore, the core idea here is to extract two crucial pieces of information from the titles: `series_key` and `series_index`. The former is used to determine whether they belong to the same series, and the latter is used to determine their position within that series: `series_key` can be understood as the "identifier of the series." As long as the `series_key` is the same, then these articles naturally belong to the same set. `series_index`, on the other hand, is more like a sequential number within this set, used to express chronological relationships, such as the first article, the second article, and the third article.
With these two pieces of information, the structure of a series is already basically computable. However, reality is not always so uniform; different articles at different stages may use different naming conventions, such as Chinese numerals, Arabic numerals, or English forms like Part 1, Part 2. Without handling, these differences can cause the same series to be split into multiple discontinuous sets.
Therefore, in implementation, the titles need to be normalized. Using simple regular expressions, different forms of numbering are uniformly converted into the same numerical structure; for example, "一、二、三" and "1、2、3" are uniformly mapped to integer sequences. This process is not complex, but its significance lies in allowing "human writing habits" to be stably mapped to a "system-calcifiable structure." After completing this layer of parsing, the next step is to truly enter the data layer processing, which involves using article-index.json to perform global matching.
The `article-index.json` file here doesn't primarily store relationships; rather, it serves as a lightweight article index table. It records basic information about all articles in the blog, including their titles, URLs, and article IDs. This allows the system to iterate through all articles at runtime and then perform the same series identification logic on each article.
Once the identification is complete, simply filter out articles with the same series_key, and then sort them according to series_index; a complete series structure will then be formed naturally.
At this point, series recognition no longer relies on any complex external systems, but is entirely accomplished by "title rules + lightweight indexing". Essentially, it makes the structural relationships originally hidden in writing habits explicit, allowing the system to stably reconstruct these relationships at runtime.
Once this layer is completed, the rest becomes very straightforward: how to use this structured series of results in PHP to calculate the current article's position and generate navigation relationships for the previous and next articles.
Rendering of the 4-Series Article Navigation and its PHP Implementation
In terms of UI, I didn't design the series navigation as a complex list. Instead, I tried to keep it lightweight, retaining only three core pieces of information: the series name, the current progress (e.g., 4/8), and the jump to the previous/next post. The display effect is similar to:
📚 Series Articles: The Awakening of the Voice (4/8) ← Previous Article Next Article →
The reason for this is simple: the core value of series navigation is not to display all the content, but to provide a "path to continue reading." Expanding the entire series list would actually interrupt the current reading flow.
In WordPress's implementation, this part is achieved through... the_content The filter is used to perform the insertion. Specifically, it dynamically concatenates an HTML fragment during the content output process and then inserts it into the article text.
Here's a crucial but easily overlooked detail: the order of insertions. In the current blog structure, besides the series navigation, there's also a "Content Structure Hints" module, which guides users through the blog's knowledge map system.

From a purely technical perspective, both modules can be easily appended to the end of the article, but from a reading experience perspective, this order is actually not ideal.
The final design prioritizes the series article navigation over the content structure cues. In other words, after the main article text, users first see the "continuous reading path for the current series" (located as shown in the red box in the image below), and only then do they see the "content structure cues."

This order is not arbitrary, but a very clear choice of reading priority: the series navigation solves the problem of "continuing to read the same linear content", while the structure prompts solve the problem of "jumping out of the current content system and entering the global knowledge structure".
Providing a continuous path while the user is still reading aligns better with natural reading behavior; while providing a structured entry point after the user has finished reading is more like a "supplementary navigation when exiting".
Therefore, in implementation, through control the_content The filter priority and the HTML splicing order ultimately ensure that the Series Card always appears before the content structure prompts, thus 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 topic directory:wp-content/themes/argon-theme-master/cache/article-index.json):
function extract_series_info(title) {
// 去掉中文全角空格title = trim(str_replace(' ', ' ', title));
/*
* 匹配形式:
* xxx(一)
* xxx(二)
* xxx(十)
*/
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
];
}
}
/*
* 匹配形式:
* xxx (1)
* xxx Part 1
* xxx part 2
*/
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()) {
// 只处理普通文章
if (get_post_type() != 'post') {
returncontent;
}
// 当前文章 ID
current_id = get_the_ID();
// article-index.json 路径json_path = get_template_directory() . '/cache/article-index.json';
if (!file_exists(json_path)) {
returncontent;
}
json = file_get_contents(json_path);
if (!json) {
returncontent;
}
articles = json_decode(json, true);
if (!articles || !isset(articles[current_id])) {
returncontent;
}
// 当前文章标题
current_title =articles[current_id]['title'];
// 提取系列信息current_series = extract_series_info(current_title);
// 不是系列文章
if (!current_series) {
return content;
}series_key = current_series['series_key'];
// 收集同系列文章series_articles = [];
foreach (articles asid => article) {
if (!isset(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,
'title' =>article['title'],
'url' => article['url'],
'index' =>series_info['series_index']
];
}
}
// 系列数量不足2篇,不显示
if (count(series_articles)<2) {
returncontent;
}
// 按序号排序
usort(series_articles, function(a, b) {
returna['index'] <=> b['index'];
});
// 定位当前文章current_position = 0;
total = count(series_articles);
foreach (series_articles asi => article) {
if ((string)article['id'] === (string)current_id) {current_position = i;
break;
}
}
// 上一篇 / 下一篇prev_article = null;
next_article = null;
if (current_position > 0) {
prev_article =series_articles[current_position - 1];
}
if (current_position < total - 1) {next_article = series_articles[current_position + 1];
}
// UI(超紧凑版)
series_card = '
<div class="series-card" style="
margin:16px 0;
">
<div style="
display:inline-flex;
align-items:center;
gap:12px;
flex-wrap:wrap;
padding:6px 12px;
background:#f8fafc;
border:1px solid #e5e7eb;
border-radius:8px;
font-size:13px;
line-height:1.2;
color:#374151;
">
<span style="
font-weight:600;
color:#111827;
white-space:nowrap;
">
📚 系列文章:' . esc_html(series_key) . '(' . (current_position + 1) . ' / ' .total . ')
</span>
';
// 上一篇
if (prev_article) {series_card .= '
<a href="' . esc_url(prev_article['url']) . '" style="
color:#2563eb;
text-decoration:none;
font-weight:500;
white-space:nowrap;
">
← 上一篇
</a>
';
} else {series_card .= '
<span style="
color:#9ca3af;
white-space:nowrap;
">
← 第一篇
</span>
';
}
// 下一篇
if (next_article) {series_card .= '
<a href="' . esc_url(next_article['url']) . '" style="
color:#2563eb;
text-decoration:none;
font-weight:500;
white-space:nowrap;
">
下一篇 →
</a>
';
} else {series_card .= '
<span style="
color:#9ca3af;
white-space:nowrap;
">
最后一篇 →
</span>
';
}
series_card .= '
</div>
</div>';
// 放在内容结构提示前面
returncontent . series_card;
}
returncontent;
}
add_filter('the_content', 'add_series_card_after_content', 5);
Final result demonstration:

Note: The "Series Article Navigation" card will only be displayed within a series of articles.
5. A lightweight yet complete reading structure patch
From an implementation perspective, the "series article cards" feature is essentially just a runtime calculation based on article-index.json and title rules. It doesn't introduce new data structures or add extra system complexity. It's more like a small functional layer added on top of the existing blog structure.
But its significance lies not in the code itself, but in filling a long-standing gap in the reading experience: continuity between articles within the same series. Before its advent, series relationships were largely confined to the writing level. Even when a user entered an article within a series, they often needed to rely on "related articles," return to the list page, or directly search for the series name to continue reading; the path was unclear, even disjointed. Now, through the "previous" and "next" structure of the "series article cards," this has changed: reading no longer depends on "selecting again," but rather progresses naturally along a predetermined order.
In terms of implementation, this layer of capability is also kept as lightweight as possible. The article-index.json provides a site-wide article index, the title rules are responsible for extracting series_key and series_index, PHP performs the minimum necessary calculations at runtime, and WordPress is responsible for the final output through the_content filter.
The entire process did not introduce any additional database structure or complex recommendation logic; it essentially reconstructed a "sequential relationship" using existing information.
Within the broader blog ecosystem, this layer currently exists independently. It is entirely built upon article-index.json, without relying on any additional semantic system or introducing more complex relational calculations.
The `article-index.json` file serves as the site-wide article index, while the "series article cards" add an extra layer of "sequential relationship" on top of this index. In other words, it doesn't care whether the content is similar or the topics are related; it only cares about one thing: whether they belong to the same series and their position within that series.
At this stage, this is sufficient to form a complete reading experience.
这倒是个不错的思路,回头研究借鉴一下。
话说才发现你居然换了配色呢
换配色是因为文章正文下面新增”相关文章”功能的背景块不突出,颜色怎么改都差点意思,所以干脆把主题的配色改了来配合,这才和谐了一点。美观什么的,是我最不擅长的了。