How to find backlinks using Eleventy (11ty)
I recently switched to using Eleventy to generate my blog. Because Eleventy is very easy to extend, I was able to add more features to my blog - including backlinks.
In a post’s sidebar, there is a list of other posts that link to the current post. This is an effective way to allow readers to find related content.
To implement this, I created a small plugin that adds a filter to find which items in a collection link to the given URL. To find links in HTML, I used the JSDom library.
Whilst there are existing backlink plugins, they didn’t meet my purposes.
eleventy-plugin-backlinks
, for example, only finds links made using
wikilinks-style markup (ie: [[Other Post Name]])
). I wanted backlinks to
work with any link in a post - whether the post is markdown, HTML, or something
else.
The below code should work with any template engine, including Liquid and Nunjucks.
Finding Backlinks #
Dependencies #
You need to install JSDom:
npm install --save jsdom
.eleventy.js #
In the eleventy config, you need to add our new plugin:
const pluginBacklinks = require("./plugins/backlinks.js");
module.exports = function(eleventyConfig) {
eleventyConfig.addPlugin(pluginBacklinks);
// You can only have one module.exports in a configuration file,
// so make sure you add the above line to your existing one.
}
backlinks.js plugin #
This is the file for the plugin. It contains getLinks
to extract links from
HTML and a plugin function to register a filter.
const { UserConfig } = require("@11ty/eleventy");
const { JSDOM } = require("jsdom");
const hostname = "blog.rubenwardy.com";
const cache = {};
/**
* Extract links from html, not including hash parts
*/
function getLinks(html) {
if (cache[html]) {
return cache[html];
}
const dom = new JSDOM(html);
const document = dom.window.document;
const result = new Set([...document.querySelectorAll("a[href]")]
.map(x => {
let href = x.getAttribute("href");
// Normalise internal links
const url = new URL(href, `https://${hostname}`);
if (url.hostname == hostname) {
return url.pathname;
}
url.hash = "";
return url.toString();
}));
cache[html] = result;
return result;
}
module.exports = (eleventyConfig) => {
eleventyConfig.addFilter("links_to", async function(collection, target) {
return collection.filter(item => getLinks(item.content).has(target));
});
};
Inside post layout #
This is how you use the links_to
filter to get backlinks inside a post layout
that uses liquid:
{% assign backlinks = collections.post | links_to: page.url %}
<!-- An empty list isn't false-y in Eleventy liquid -->
{% assign backlinks_count = backlinks | size %}
{% if backlinks_count > 0 %}
<aside id="backlinks">
<h3>Links here</h3>
<ul>
{%- for post in backlinks -%}
<li>
<a href="{{ post.url }}">{{ post.data.title }}</a>
</li>
{%- endfor -%}
</div>
</aside>
{% endif %}
Comments
Great post! I recommend using linkedom for parsing/processing HTML instead of JSDom though. Lightweight and fast!