This site runs best with JavaScript enabled.

Handling External Links for MDX files in Gatsby

December 10, 2021 • 1 min read • 

ssg,react,gatsby

Photo by Eric Mclean on Unsplash

Typically, when you’re writing markdown files, you’d sometimes want to include a link that takes the user from your current page to some other page. Sometimes, these links can be internal (i.e. redirects user to a page on your site) or external (i.e. https://www.twitter.com). What is the best way to set this up globally so that you don’t have to manually configure it on every markdown files?

Option #1: Creating the Link component

This component basically checks if the link is prefixed with your site’s url or “/”. Then decides if it should open in a new tab or not:

const ExternalLink = props => {
  if (
    props.href.includes("https://dannielhansel.com/") ||
    props.href[0] === "/"
  ) {
    return (
      <a {...props} href={props.href}>
        {props.children}
      </a>
    )
  }
  return (
    <a {...props} href={props.href} target="_blank" rel="noopener noreferrer">
      {props.children}
    </a>
  )
}

Pass the component object into MDXProvider

Then, you can pass it into the MDXProvider so every markdown link syntax can be controlled:

const components = {
    a: ExternalLink,
}

const ExampleLayout = ({children}) => (
    <>
        <MDXProvider components={components}>
            <MDXRenderer>{body}</MDXRenderer>
        </MDXProvider>
    <>
)

Option #2: Using DRY principle

Another way is to create a reusable component and just import it whenever you need it. You can also use this outside of your markdown files (i.e. your social links on your site).

// inside /src/components/link.js
import * as React from "react"
import { Link as GatsbyLink } from "gatsby"

// Since DOM elements <a> cannot receive activeClassName
// and partiallyActive, destructure the prop here and
// pass it only to GatsbyLink
const Link = ({ children, to, activeClassName, partiallyActive, ...other }) => {
  // Tailor the following test to your environment.
  // This regex assumes that any internal link (intended for Gatsby)
  // will start with exactly one slash, and that anything else is external.
  const internal = /^\/(?!\/)/.test(to)

  // Use Gatsby Link for internal links, and <a> for others
  if (internal) {
    return (
      <GatsbyLink
        to={to}
        activeClassName={activeClassName}
        partiallyActive={partiallyActive}
        {...other}
      >
        {children}
      </GatsbyLink>
    )
  }

  return (
    <a href={to} {...other} target="_blank" rel="noopener noreferrer">
      {children}
    </a>
  )
}

export default Link

Then you can import this component in your markdown file just like a regular javascript file.

// inside /content/handling-external-links-for-mdx-files-in-gatsby.mdx

import Link from "../../src/components/link"

// ---
// ... frontmatter stuff
// ---
;<Link to="https://twitter.com">Twitter</Link>

Option #3: Use the component as shortcode

Another way is to kind of combine option 1 and 2, or known as “shortcode”. It’s basically a component that is available in every markdown file without you having to import it every time.

import Link from '../components/link'

const components = {
    Link: ({children, to, ...props}) => (
        <Link to={href} {...props}>
            {children}
        </Link>
    ),
}

const ExampleLayout = ({children}) => (
    <>
        <MDXProvider components={components}>
            <MDXRenderer>{body}</MDXRenderer>
        </MDXProvider>
    <>
)

Then in your markdown file:

// inside /content/handling-external-links-for-mdx-files-in-gatsby.mdx

// ---
// ... frontmatter stuff
// ---

<Link to="https://twitter.com">Twitter</Link>

Recap:

Here’s what your Post template would look like:

// in your templates/post.js

const ExternalLink = props => {
  if (
    props.href.includes("https://dannielhansel.com/") ||
    props.href[0] === "/"
  ) {
    return (
      <a
        {...props}
        href={props.href}
        className="font-semibold text-accent hover:underline hover:underline-offset-4 cursor-pointer"
      >
        {props.children}
      </a>
    )
  }
  return (
    <a
      {...props}
      href={props.href}
      className="font-semibold text-accent hover:underline hover:underline-offset-4 cursor-pointer"
      target="_blank"
      rel="noopener noreferrer"
    >
      {props.children}
    </a>
  )
}

const components = {
    // other components ...
    a: ExternalLink,
}

const BlogPostTemplate = ({children}) => {
    const { frontmatter, body, timeToRead, excerpt, fields } = data.mdx
    return (
        <>
            <MDXProvider components={components}>
                <MDXRenderer>{body}</MDXRenderer>
            </MDXProvider>
        <>
    )
}

export const pageQuery = graphql`
  query BlogPostQuery($id: String) {

    mdx(id: { eq: $id }) {
      id
      excerpt(pruneLength: 160)
      body
      timeToRead
      fields {
        slug
      }
      frontmatter {
        slug
        title
        author
        tags
        description
        date(formatString: "Do MMM YYYY")
      }
    }
  }
`
export default blogPost

Discuss on TwitterEdit on GitHub

Danniel Hansel

Hey there 👋, I'm Danniel. I'm passionate about technology, solving problems, design and research. This site, is like me! It's a work in progress, so you'll see things come and go as I learn and change based on new information.