Adding Backlinks to a NextJS site with Content Layer

One of the things I've loved about using Content Layer for my personal site is that once you have it integrated it's very easy to generate backlinks for any content type. So easy in fact, that this post is going to be a little underwhelming πŸ˜….

I've read great articles about thee value and history of "two-way links" like the great illustrated history that Maggie Appleton wrote, and I knew I wanted to integrate them into my site so that someday when there is enough content here some internet wanderer can have true rabbit holes to dive down.

The getBacklinks(slug) function

I was worried it would be hard to implement but it took 6 lines of code thanks to the wonderfully useful allDocuments export from Content Layer. Let me show you my getBacklinks() function and then we'll walk through it:

// lib/markdown.ts
import { allDocuments } from "contentlayer/generated";

export function getBacklinks(slug: string) {
    const backlinkingDocs = allDocuments
	    .filter(doc => doc.body.raw.includes('[['+slug)) as DocumentTypes[]
    
    return backlinkingDocs.map(doc => ({
	    title: doc.title,
	    url: doc.url,
	    type: doc.type
	}))
}

This function takes a document's slug as a string and filters all of the documents brought in through Content Layer for the ones that include an Obsidian link to thee current slug, which we can find by searching for the opening characters of an Obsidian linkβ€”[[β€” plus the slug. Then we return stripped-down versions of these objects, with just the data we need to generate backlinks.

Using the function

Now let's look at my Post template page for an example of how I use this function. I've added this code to all my content type templates.

// /pages/posts/[slug].tsx
export const getStaticProps: GetStaticProps = (context) => {
  const { slug } = context.params as IParams
  const post = allPosts.find((post) => post._raw.sourceFileName.includes(slug)) as PostMdOrMdx

  const backlinks = getBacklinks(slug) // Here is we request some backlinks

  post.body.code = obsidianLinksPostProcess(post.body.code, allDocuments)

  return {
    props: {
      post,
      backlinks, // ...and pass them into our page
    },
  }
}

// ...

const PostTemplate: NextPageWithLayout = (props) => {
  const { post, backlinks } = props as IPostParams;
    
  return (<>
    // ...more Post template content
        <MdxBody content={ post.body.code }/>
        {backlinks.length > 0 && (<>
        <h2>Other content that links to this</h2>
        <ul>
          {backlinks.map(backlink => (
            <li key={backlink.url}>
              <Link href={backlink.url}><a>
                <strong>{ backlink.type.replace("Post", "Note") }: </strong>{ backlink.title }
              </a></Link>
            </li>
          ))}
        </ul></>)}
      </article>
    </>
  )
}

So all I have to do is pass slug into getBacklinks within my getStaticProps function, and I will receive an array of Backlinks (I made a custom type for this, not shown but available in the source code). In the returned page content, I check if there are backlinks and create a section if there are, iterating over each backlink to create a simple list item containing a type label and a link to document.

And just like that, we have automatic backlinks!

See them in action

To see them in action, click the link to my personal site project page in the first paragraph: this post will be listed at the end in the backlinks list.

I haven't tested how much this slows down build times on large sites, but using the allDocuments export has been very effective for me on my site. This is another reason why I really can recommend Content Layer as an effective tool even in its early stages. Happy coding πŸ€™πŸ»