Astro, fix .md in links
By Flavio Copes
How to fix Markdown links ending in .md breaking in Astro, using a rehype plugin so internal Obsidian-style links resolve to the right page URLs.
If your Markdown links end in .md, they break when Astro builds the site: pages have clean URLs, so a link pointing to a .md file leads to a page that doesn’t exist. The fix is a rehype plugin that rewrites those links at build time.
Here’s how I got into this. Using Obsidian to edit my Markdown files, I’ve started to link lessons using Obsidian’s own internal linking.
It’s great because with Obsidian link tracking I can move a file around, change folder or filename, and links don’t break.
Links however use a .md extension, since we’re linking markdown files here:
[3-absolute-positioning](../css-positioning/3-absolute-positioning.md)
Why do these links break?
Obsidian links files on disk. Astro takes those files and turns them into pages, and pages have URLs without the extension: the file 3-absolute-positioning.md becomes a URL like /css-positioning/3-absolute-positioning/.
But Astro doesn’t rewrite the links inside your Markdown content. The href stays exactly as you wrote it, .md included. In Obsidian the link works. In the browser, it’s a 404.
The fix
I found this rehype plugin for Astro: https://github.com/vernak2539/astro-rehype-relative-markdown-links
rehype plugins run after your Markdown is converted to HTML, and they can transform that HTML before Astro writes the page. This one finds links pointing to .md files and rewrites them to the corresponding page URLs.
It fixes this exact problem and I’m so grateful this exists, but it didn’t (at the time of writing) install on Astro 3, it required Astro 2. Maybe now it’s updated.
Anyway since it’s a single file I copied the file locally and imported it in my Astro config, in the markdown.rehypePlugins array:
import { defineConfig } from 'astro/config'
import rehypeMarkdownLinks from './rehype-relative-markdown-links.mjs'
export default defineConfig({
markdown: {
rehypePlugins: [rehypeMarkdownLinks]
}
})
One thing I had to adjust
Only thing I noticed is, maybe because I use content collections, my content collection folder name was prepended to the URLs the plugin generated. The link resolved to the file path, not to the final page URL.
So I hardcoded a little custom fix in the code that only works in my use case, but well, job done.
If you hit the same problem, compare the URL the plugin generates with the actual URL of the page. The difference tells you exactly what to strip.
Related posts about astro: