Create related page links
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
1
because 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
1
added to the relevancy score. If next directory level also matches,2
is 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>
` : ''}