Words by Vernacchia

Relative Markdown Paths in Astro via Rehype


I stared using Astro and it blew my mind. It did everything I needed and even things I didn’t know I needed. It upped my static site/SSG game very quickly.

After a couple blog posts I noticed something weird. When I clicked on a link, it gave me a 404. What gives??

I investigated further and realised that Astro doesn’t modify the MD/MDX content of a Content Collection. This meant when I had a Markdown link that looked like this:

[relative link](./other-markdown.md)

It would be rendered exactly the same:

<a href="./other-markdown.md">relative link</a>

I assumed that Astro would transform these paths when it built the site. Unfortunately, I made an ass out of me (not you though, you’re perfect).

Turns out I wasn’t the only one surprised by this “missing” functionality. There’s multiple issues (#5682, #5680) and an RFC (#424).

After reviewing all the issues and discussions it didn’t seem like the Astro team would be focusing on fixing this problem anytime soon, so I decided to try my hand at it.

🚨 Update: The Astro team may be solving this using Assets. It’s still experimental, but it’s worth trying it out! I still learned a lot!

🚨 Update 2: I tried the experimental Assets feature out, but it didn’t seem to work (I also misunderstood). Maybe we’ll see if it can go directly in Astro 🤷

Enter rehype (and remark)

remark and reyhpe are part of the @unifiedjs collective and are used to transform Markdown and HTML respectively. They can even be used in combination.

rehype:

…is an ecosystem of plugins that work with HTML as structured data, specifically ASTs (abstract syntax trees). ASTs make it easy for programs to deal with HTML. We call those programs plugins. Plugins inspect and change trees.

remark:

…is a tool that transforms markdown with plugins. These plugins can inspect and change your markup.

TL;DR - rehype = HTML transforms. remark = Markdown transforms.

Note: As I write this, I realise the plugin I created could be a Remark plugin as well. I’ll explore that in the rehype vs remark section below.

Anyway, these are some of the best frameworks that support modifying of Markdown and HTML via AST transforms, which is exactly what I needed to do.

Astro also supports both rehype and remark plugins! We’re in luck, so let’s get started.

🚨 I built this for Astro exclusively and made a couple assumptions. They are:

  1. You are rendering a static site (i.e. not using SSR)
  2. You have a content collection residing at src/content/<content_collection>
  3. You have a page that renders the above content collection at src/pages/<content_collection>/[...slug].astro

Links: GithubNPM

Essentially, this plugin will look through the AST created by rehype and look for the href nodes. Once it finds these nodes, it will:

  1. Check if the path is a candidate to be transformed. The following criteria have to be met:
    • The path is not empty
    • The path does not represent an absolute path
    • The file has an extension of .md or .mdx
  2. Check to see if the path is a valid one (i.e. the relative file exists in relation to the current file)
  3. Determine if the relative file has a custom slug defined in the frontmatter, and use it if available
  4. Build and return the final path that is compatible with the final HTML generated by Astro

I didn’t go too in-depth, but feel free to check out the code.

Usage

If you want to try it out, simply install the plugin and follow the configuration steps below.

yarn add astro-rehype-relative-markdown-links

astro.config.mjs

import rehypeAstroRelativeMarkdownLinks from "astro-rehype-relative-markdown-links";

// ...everything else

export default defineConfig({
    // ...everything else
    markdown: {
        rehypePlugins: [rehypeAstroRelativeMarkdownLinks],
    },
});

After integrating the plugin, the following link that is inside a src/content/blog/post.md file:

[relative link](./other-markdown.md)

Would be rendered as:

<a href="/blog/other-markdown">relative link</a>

rehype vs remark

Like I mentioned above, should this plugin be a rehype or remark plugin? As it stands, astro-rehype-relative-markdown-links is a rehype plugin. Did I make the right choice??? 🤔

I think I did. Let me explain.

remark is used to transform markdown files, while rehype transforms HTML. It really could’ve gone either way (i.e. transform path in Markdown or HTML), but I choose to transform the link (i.e. href) when converting the AST to the final HTML output. Thus, rehype was the choice.

Until next time...