Backlinks in Astro

evergreen
Written on Updated on

Tagged with astro, code, guide, and pkm

Backlinks show the connections between notes by indicating which notes reference others, and are a fundamental feature of most personal knowledge management systems and digital gardens.

This guide details how to add backlinks to Astro. I've also written a guide on how to do this in Eleventy.

1. Get the notes

Let's begin by getting our notes. In the component script at the top of our file, add the following:

import { getCollection } from 'astro:content';

const allNotes = await getCollection('notes');

My collection is simply called 'notes', so adjust this to match your content collection name.

Note: This assumes we're using content collections, but it can be adapted to work for file-based routing by importing notes using import.meta.glob instead.

2. Extract links from the note content

Next, we need to find and extract any links in our notes. Regex is useful here, as markdown links follow a consistent format, [text](url).

We'll also create an empty object to hold the links our regex finds.

const linkRegex = /\[.*?\]\((.*?)\)/g;
const allLinks = {};

Now we need to iterate over each note, checking the body for links.

allNotes.forEach((note) => {
  const links = [...note.body.matchAll(linkRegex)];
});

Note: Again, if using file-based routing, we would need to use the rawContent() function to get the note content.

Each of these matched links comes with a few properties, but we want the URL of the link itself, which is the second property. Remember, this is zero-indexed, so we'll use link[1] to get it.

Within the allNotes.forEach() loop, iterate across each note's links and extract the link URL:

if (links) {
  links.forEach((link) => {
    const linkUrl = link[1];
  }
}

We need to populate our allLinks object with each extracted link. First, we'll check the link doesn't already exist in the object, and initialise it with an array if not, then push some data into the array.

Within the links.forEach() loop, add the following:

if (linkUrl) {
  if (!allLinks[linkUrl]) {
    allLinks[linkUrl] = [];
  }
  allLinks[linkUrl].push({
    frontmatter: note.data,
    url: `/notes/${note.id}`
  });
}

Here, we're extracting the frontmatter with the note's data object, and building a URL with the note's id. Adjust this to match your note URL format.

3. Check for the current note

Now we have an object containing all the links in every note, we need to cross-reference them with the current note's URL. We can get this by using Astro's built-in Astro.url object.

const { pathname } = Astro.url;

By destructuring the pathname from the current URL, we can populate a new array with any matching entries in our allLinks object:

const backlinks = allLinks[pathname] || [];

This gives us an array of backlinks for each note.

4. Generate HTML

With this in the code fence at the top of a component, we can add some markup to the component template, iterating over the backlinks array to populate a list of backlinks for each note.

{backlinks.length > 0 && (
  <ul>
    {backlinks.map((backlink) => (
      <li>
        <a href={backlink.url}>{backlink.frontmatter.title}</a>
      </li>
    ))}
  </ul>
)}

5. Final code

Here's the complete code.

---
import { getCollection } from 'astro:content';

const { pathname } = Astro.url;
const allLinks = {};
const allNotes = await getCollection('notes');
const linkRegex = /\[.*?\]\((.*?)\)/g;

allNotes.forEach((note) => {
  const links = [...note.body.matchAll(linkRegex)];
  
  if (links) {
    links.forEach((link) => {
      const linkUrl = link[1];

      if (linkUrl) {
        if (!allLinks[linkUrl]) {
          allLinks[linkUrl] = [];
        }
        allLinks[linkUrl].push({
          frontmatter: note.data,
          url: `/notes/${note.id}`
        });
      }
    });
  }
});

const backlinks = allLinks[pathname] || [];
---

{backlinks.length > 0 && (
  <ul>
    {backlinks.map((backlink) => (
      <li>
        <a href={backlink.url}>{backlink.frontmatter.title}</a>
      </li>
    ))}
  </ul>
)}

By adding this component to our note layout file, backlinks will start appearing as we interlink our notes.