Danniel Hansel.

(03) — Blogs — Tutorial

Handling External Links for MDX files in Gatsby

The easiest way to open links in a new tab when using gatsby-plugin-mdx.

Scroll ↓Fullstack Software Engineer — Dallas, TX
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?

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
Next page — (03)Blogs