Related content
List pages related to the current one by keywords, tags, dates or headings, with configurable indices and weights.
related scores the pages of a list against a page by the values they share, and returns the best matches:
{% set related = related(pages=site.regular_pages, page=page, limit=5) %}
{% if related %}
<h2>See also</h2>
<ul>
{% for p in related %}<li><a href="{{ p.rel_permalink }}">{{ p.title }}</a></li>{% endfor %}
</ul>
{% endif %}page defaults to the page being rendered. The page itself is never in the result.
Configuration#
Each index compares one value of the pages: a front matter key or a taxonomy. The default uses keywords (weight 100), date (10) and, when the site has a tags taxonomy, tags (80):
[related]
includeNewer = false
threshold = 80
toLower = false
[[related.indices]]
name = "keywords"
weight = 100
[[related.indices]]
name = "tags"
weight = 80
[[related.indices]]
name = "date"
weight = 10related:
includeNewer: false
indices:
- name: keywords
weight: 100
- name: tags
weight: 80
- name: date
weight: 10
threshold: 80
toLower: false{
"related": {
"includeNewer": false,
"indices": [
{
"name": "keywords",
"weight": 100
},
{
"name": "tags",
"weight": 80
},
{
"name": "date",
"weight": 10
}
],
"threshold": 80,
"toLower": false
}
}threshold- The minimum score, from 0 to 100 (below).
includeNewer- Include pages published after the page. Without it, the oldest page has no related pages.
toLower- Compare values case-insensitively, in every index.
indices- What to compare:
name: the front matter key or taxonomy, such astags,keywords,date,sectionor a param.weight: how much a shared value counts; 0 turns the index off.type:basic(the default) compares values;fragmentscompares heading ids (below).pattern: for a date index, the Go layout the dates are compared in:2006(the default) relates the pages of the same year,2006-01of the same month.cardinalityThreshold: from 0 to 100; leave out the values that more than this percentage of the pages with a value in the index have (a tag on every page relates nothing). 0 keeps them all.toLower: compare this index’s values case-insensitively.applyFilter: for a fragments index, give each result only its matched headings.
The configuration of each language is its own, under [languages.<key>.related].
Scores#
Each value a page shares with the current one adds the weight of its index. A page that shares n values is related when the average weight, on a scale of 0 to 100 between the lowest and the highest weight (with 0), is at least threshold / n. The more values a page shares, the lower the bar: with the default configuration, a page that shares only the year (10) is not related, one that shares a tag (80) is, and so is one that shares a tag and the year (90 / 2 = 45, at least 80 / 2 = 40).
The result is sorted by the summed weight, then the newest first, then by name.
Searching#
related takes more than the page:
indices- Search only these indices:
indices=["tags"]. keywords- Values to search, by index name, besides the page’s:
keywords={"tags": ["rust", "cli"]}. A single value is a list of one. fragments- Heading ids to search in the fragments indices:
fragments=["installation"]. pagepage=nonesearches thekeywordsandfragmentsalone, with the configuration of the render’s language:
{% set rust = related(pages=site.regular_pages, page=none, keywords={"tags": ["rust"]}) %}limit- At most this many pages.
Headings#
An index with type = "fragments" compares the ids of the pages’ headings (## Install has the id install) and the values of its front matter key, read as heading ids: a page with fragmentrefs = ["install"] is related to the pages with an install heading.
[related]
includeNewer = true
[[related.indices]]
name = "fragmentrefs"
type = "fragments"
applyFilter = true
weight = 80related:
includeNewer: true
indices:
- applyFilter: true
name: fragmentrefs
type: fragments
weight: 80{
"related": {
"includeNewer": true,
"indices": [
{
"applyFilter": true,
"name": "fragmentrefs",
"type": "fragments",
"weight": 80
}
]
}
}With applyFilter, when headings matched, the fragments of each page in the result holds only those headings, so a list can link to the sections themselves:
{% for p in related(pages=site.regular_pages, limit=5) %}
{% for h in p.fragments.headings %}
<a href="{{ p.rel_permalink }}#{{ h.id }}">{{ p.title }}: {{ h.title | safe }}</a>
{% endfor %}
{% endfor %}Each heading has its id, level, title (HTML) and the headings below it; fragments.identifiers lists the ids. A page that another index found lists the matched headings it has, often none. When no heading matched, or without applyFilter, the pages are as everywhere else: in a layout, fragments holds all their headings; in a shortcode, page_fragments(page=p) gives them.
Comparing headings needs the headings of every page in the list, which fugo reads from their content once per list. It works in shortcodes too: a page’s own headings then come from its content without its shortcodes.