Skip to main content

How to find backlinks using Eleventy (11ty)

Sidebar

I recently [switched to using Eleventy](/2023/10/27/switched-to-eleventy/) to generate my blog. Because Eleventy is very easy to extend, I was able to add more features to my blog - including backlinks.

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.

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.
}

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 %}
rubenwardy's profile picture, the letter R

Hi, I'm Andrew Ward. I'm a software developer, an open source maintainer, and a graduate from the University of Bristol. I’m a core developer for Luanti, an open source voxel game engine.

Comments

Leave comment

Shown publicly next to your comment. Leave blank to show as "Anonymous".
Optional, to notify you if rubenwardy replies. Not shown publicly.
Max 1800 characters. You may use plain text, HTML, or Markdown.