
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