Make External MDX Links Open in a New Tab in Astro

Web Reaper avatar

Web Reaper

4 min read

Cover for Make External MDX Links Open in a New Tab in Astro

When writing MDX blog posts in Astro, you’ll likely want any external links to open in a new tab. This is a common practice to keep users on your site while also allowing them to explore other content.

In this guide, I’ll show you how to create a simple component that will make all your external links open in a new tab and with whatever attributes your heart desires.

The Goal

In our .mdx posts, we want external links to open in a new tab, while other links open in the same tab.

[This should open in the same tab](/themes/amplify/)
[This should open in a new tab](https://astro.build/themes/details/amplify/)

MDX Components

If you aren’t already aware, MDX allows you to specify components to be used instead of an HTML element. This is the feature we will use to replace the <a> tag with our custom component.

The goal is to have our custom component add the attributes target="_blank" and rel="noopener noreferrer" to any external links so that they safely open in a new tab. First create a new .astro file somewhere, likely in the src/components directory. I use src/components/ExternalLink.astro.

src/components/ExternalLink.astro
---
// accept the href prop from the MDX file
// check if the link is external
// if it is, add target="_blank" and rel="noopener noreferrer"
// maintain a <slot /> for the link text
---

Basic Setup

First lets just go ahead and make it accept the normal href parameter and put it into an anchor tag.

src/components/ExternalLink.astro
---
// accept the href prop from the MDX file
const { href }: Record<string, any> = Astro.props;
---
<!-- maintain a <slot /> for the link text -->
<a href={href}><slot /></a>

A link is external to your website if:

  • It doesn’t include your domain
  • It doesn’t start with /
  • It doesn’t start with #

So lets go ahead and add that checking in.

src/components/ExternalLink.astro
---
// accept the href prop from the MDX file
const { href }: Record<string, any> = Astro.props;
const domain = import.meta.env.SITE; // pulls from astro.config.mjs
// check if the link is external
if (!href.includes(domain) && !href.startsWith("/") && !href.startsWith("#")) {
// if it is, add target="_blank" and rel="noopener noreferrer"
}
---
<!-- maintain a <slot /> for the link text -->
<a href={href}><slot /></a>

Adding Attributes

The last thing to do is add the target="_blank" and rel="noopener noreferrer" attributes to the anchor tag. Below is the completed component.

src/components/ExternalLink.astro
---
// accept the href prop from the MDX file
const { href }: Record<string, any> = Astro.props;
const domain = import.meta.env.SITE; // pulls from astro.config.mjs
let attr: { target?: string; rel?: string } = {};
// check if the link is external
if (!href.includes(domain) && !href.startsWith("/") && !href.startsWith("#")) {
// if it is, add target="_blank" and rel="noopener noreferrer"
attr["target"] = "_blank";
attr["rel"] = "noopener noreferrer";
}
---
<!-- add attributes and maintain a <slot /> for the link text -->
<a href={href} {...attr}><slot /></a>

So, how do I use it?

Now that you have your ExternalLink.astro component, you can have Astro use it instead of the default anchor tag. This is done in the same way we added our previous Aside component. We will specify the component in the file where the <Content /> is rendered.

Production-ready Astro Templates

Astro website templates

Templates with tons of features others leave out. I18n, CMS, animations, image optimization, SEO, and more.

Blog […slug].astro edits

I’ll base this example off of the blog starter project, where the content is rendered in src/pages/blog/[...slug].astro. Here’s the slight modifications you need to make.

src/pages/blog/[...slug].astro
---
import { type CollectionEntry, getCollection } from "astro:content";
import BlogPost from "../../layouts/BlogPost.astro";
import ExternalLink from "../../components/ExternalLink.astro";
export async function getStaticPaths() {
const posts = await getCollection("blog");
return posts.map((post) => ({
params: { slug: post.slug },
props: post,
}));
}
type Props = CollectionEntry<"blog">;
const post = Astro.props;
const { Content } = await post.render();
---
<BlogPost {...post.data}>
<Content components={{ a: ExternalLink }} />
</BlogPost>

So, what now?

That’s it! Now any external links in your MDX posts will automatically open in a new tab. Happy coding. 🚀

C O S M I C