Create related page links
(updated )
646 words, 4-minute read
There are a number of ways to show lists of pages with related content. The following sections describe the process used on this site.
Relevancy scoring #
This site uses an algorithm which assigns a relevancy score to each page when they have:
The same tags
Each tag has an associated score. The most-used tag only scores
1because it’s used across many pages. A tag used only once has a score equivalent to the total number of tags.All matching tags have their associated score added to the relevancy score.
The same directories
Pages with matching root directories have
1added to the relevancy score. If next directory level also matches,2is added and so on.
Pages with the highest relevancy scores can then be shown.
Create a function library #
The following code creates a new lib/hooks.js file containing two functions.
renderstartTagScore() #
This function is used as a processRenderStart event hook which runs once before page rendering starts. It creates a global tacs.tagScore Map object where the key is the normalized tag reference name and the value is its score based on usage. The less a tag is used, the higher its score.
lib/hooks.js
// processRenderStart hook: calculate tacs.tagScore { rel: score } Map
// lesser-used tags have a higher score
export function renderstartTagScore( tacs ) {
if (!tacs.tagList.length) return;
// maximum tag count
const countMax = tacs.tagList[0].count + 1;
// tag score Map
tacs.tagScore = new Map();
tacs.tagList.forEach(t => tacs.tagScore.set(t.ref, countMax - t.count));
}
prerenderRelated() #
This function is used as a processPreRender event hook which runs for each page before its rendered.
It examines all other pages and assigns them a relevancy score based on matching tags and directories. The post’s data object has a related array appended containing other page data objects which have a relevancy score of as least 1 ordered from highest (most relevant) to lowest (least relevant).
lib/hooks.js
// processPreRender hook: related posts, generated at pre-render time
export function prerenderRelated( data, tacs ) {
if (!tacs.tagScore || !data.filename || !data.title || !data.isHTML || !data.tags) return;
const relScore = [];
// calculate other post relevency scores
tacs.all.forEach((post, slug) => {
if (data.slug == slug || !post.filename || !post.title || !post.isHTML || !post.tags) return;
let score = 0;
// calculate matching tag scores
data.tags.forEach(dTag => {
post.tags.forEach(pTag => {
if (dTag.ref === pTag.ref) score += tacs.tagScore.get( dTag.ref );
});
});
// calculate matching directory scores
const
dDir = data.link.split('/').filter(d => d),
pDir = post.link.split('/').filter(d => d);
let i = 0;
while (i >= 0) {
if (dDir[i] && dDir[i] === pDir[i]) {
i++;
score += i;
}
else i = -1;
}
// record related
if (score) relScore.push( { slug, score } );
});
// add to related posts sorted by score
data.related = relScore
.sort((a, b) => b.score - a.score || b.date - a.date )
.map(p => tacs.all.get( p.slug ) );
}
Import the library #
You can import the lib/hooks.js functions into your publican.config.js configuration file and use them in custom event hooks:
publican.config.js excerpt
import { Publican, tacs } from 'publican';
import * as fnHooks from './lib/hooks.js';
const publican = new Publican();
// processRenderStart hook: create tacs.tagScore Map
publican.config.processRenderStart.add( fnHooks.renderstartTagScore );
// processPreRender hook: determine related posts
publican.config.processPreRender.add( fnHooks.prerenderRelated );
// build site
await publican.build();
Use in templates #
You can now examine the data.related array in any template. The following example shows up to three posts with the highest relevancy scores.
${ data?.related?.length ? `
<aside class="related">
<h2>Related posts</h2>
<nav>
${ data.related
.slice(0,3)
.map(p => `
<p><a href="${ p.link }">${ p.title }</a></p>
`)
.join('') }
</nav>
</aside>
` : ''}