Development notes
Generating related reading from stable metadata
How the related-reading rules on this site combine publication eligibility, language, tag scores, and ordering to reduce manual upkeep without replacing editorial judgment.
- Astro
- Content engineering
- Metadata
- Sorting
Related reading at the end of an article points readers toward their next question. If each article has a manually maintained list, adding a new article also means deciding which older articles should link to it. As the collection grows, it becomes easier to overlook those updates.
This site starts with metadata the articles already have, builds a set of candidates, then selects a few using fixed rules. I want those rules to explain why a card appears here, which content must never appear, and what to check when the results fall short. They do not need to pretend to understand the full text.
This note describes the implementation and local output as of October 25, 2026. It is a small set of rules based on category, tags, and dates, not a recommendation system trained on reader behavior.
Decide which articles are eligible first
Publication eligibility and relevance are separate questions.
An article page first calls getPublishedNotes(locale) to fetch articles eligible for publication in that language. This layer excludes drafts, articles without a publication date, and articles with publication dates later than the current day in Taipei. Only then does it pass the collection to selectRelatedNotes().
The latter excludes two more kinds of candidates: those in a different language from the current article, and those whose stableId matches the current article. Related reading therefore does not include the article itself or introduce another language just to fill the card slots.
There is a limit to that division of responsibility: selectRelatedNotes() does not check draft status or publication dates itself. If another caller passes in every article, the helper will not add the missing publication filter. I confirmed this distinction with controlled data, so the function name alone is no reason to treat it as a safe entry point for public-content queries.
Why the homepage, note lists, article pages, and sitemap share a publication query covers the same publication requirements. Related reading starts after those requirements have been applied and answers the next question: which articles go first.
Score candidates by category and matching tags
Each candidate receives the following score:
Add 3 points for the same category and 2 points for each matching tag.
Here, a match means an exact string match. There is no case conversion, synonym handling, or semantic analysis. Astro and astro are not treated as the same tag. Even if the same technology name appears in a title, that does not earn tag points.
Suppose all three candidates belong to the same category as the current article. Their scores would be as follows. This is a controlled example to explain the rules, not a comparison of the performance of three real articles.
| Candidate scenario | Category points | Tag points | Total |
|---|---|---|---|
| Two matching tags; published earlier | 3 | 4 | 7 |
| One matching tag; published more recently | 3 | 2 | 5 |
| No matching tags; published most recently | 3 | 0 | 3 |
The first candidate still comes first. Dates only break ties in score; a newer article does not jump ahead of one with a higher score.
The site has only one category, development, so candidates in the same language all receive identical category points. At this stage, tags are the main source of differences in score. Retaining a category weight does not mean that recommendations already span multiple categories, or that reader research has established this as the best weighting.
Make tied results predictable
Sorting compares the score first, then publishedAt, and finally stableId. When scores tie, more recently published articles come first. The stable identifier breaks the tie only when publication times also match.
I placed two candidates with the same score and publication time in a controlled collection and confirmed that stableId determined their final order. I also checked that the function left the input collection’s original order unchanged. These checks concerned the sorting contract; they did not show that one candidate was more worth reading than another.
Stable ordering makes results easier to trace. If a card changes position, the tags, dates, and candidate set offer concrete places to check before assuming the recommendation system has a mind of its own.
Not every slot needs a card, and not every card is a close match
The function returns at most two articles by default. It takes as many as are available, up to that limit, from the sorted eligible candidates without duplicating an article to fill a slot. The article template renders the related-reading section only when the list is nonempty; draft previews do not create the list at all.
There is no minimum score threshold, though. A candidate can still be selected without any matching tags as long as it remains in the candidate set. With only one category, these candidates still receive identical category points, then fall back to date order.
A visible card therefore means only that the article passed the candidate and sorting rules. It does not mean the article has been confirmed as the best thing to read next. If the collection grows and weakly related candidates keep appearing, that would be a reason to consider a minimum threshold or manual selections. There is no need to add a whole configuration system for a problem that has not appeared.
Automatic lists still need editorial judgment
Once tags affect sorting, they are no longer just labels displayed above the article. Changing a tag’s spelling may change which articles sit close to one another. Adding a batch of overly broad tags without care can also make distinct topics look equally related.
I would check the question an article actually answers before deciding whether to change its metadata. Two articles may both discuss “verification”: one teaches readers to check publication output, while the other explains how to withdraw a conclusion after an AI makes an incorrect judgment. They may share a tag without becoming interchangeable.
These rules do not read the full text, track clicks, or identify a reader’s prerequisite knowledge. They do not guarantee that an introductory article will come before an advanced one. When a specific reading path matters, a link in the body that explains why to follow it still provides more guidance than an automatic list.
Metadata changes affect the next build
Related reading is selected when Astro generates the article HTML. The cards are ordinary links, so the browser does not have to fetch data from a recommendation service. The trade-off is that after an article is added, tags change, or publication eligibility changes, existing static pages need to be rebuilt and deployed before their lists update.
During the local check, I calculated the expected list from the actual public collection, then compared it with the two cards and their order on all three language versions of the HTML semantics article. They matched. This showed that the selected pages’ output followed the rules described here. There was no click analysis, so this was not evidence that the recommendations led to more reading.
I would keep this explainable list for now, and wait for concrete mismatches before deciding whether the problem is inaccurate metadata, insufficient sorting rules, or the need for a manually arranged reading path.