Integrate the Next Link Component with Your MDX Content
This is part 10 in the Build a Blog with Next, MDX, and Tailwind series
Next provides the Link component to allow client side navigation. This is great when we’re working directly in some JSX for a component or a page, but it’s not quite as straightforward when we’re trying to create local links in our markdown. We certainly could just expose the Link
component to the MDXRemote
component and use it directly, but I prefer to use markdown style links.
In this installment of the Build a Blog with Next, MDX, and Tailwind Blog series, we’ll create a SmartLink
component that uses a standard anchor tag with a target
attribute set to _blank
for external links and the Link
component for local links.
Some Example Content
Let’s start off by modifying one of our markdown files. We’ll add 3 separate links in the first paragraph. One will link to an external website, another will link to a heading within the document, and the third will link to the route for one of our other markdown based routes.
I’ll continue to add updates to the content/hello-world.md
file. If you’ve been following along, this page should have some h2
and h3
elements that allow links to those anchors. My updates will make the first paragraph look like this.
Maybe there's a **happy little bush** that lives [right there](https://vanslaars.io). There's [nothing wrong](/blog/stuff-and-things) with having a tree as a friend. [Mountains are so simple](#section-two), they're hard.
Creating the SmartLink Component
Let’s start by adding a new React component file in our components
directory called SmartLink.tsx
with the following starting content.
import Link from 'next/link'
import {
AnchorHTMLAttributes,
DetailedHTMLProps,
FunctionComponent,
} from 'react'
export const SmartLink: FunctionComponent<
DetailedHTMLProps<AnchorHTMLAttributes<HTMLAnchorElement>, HTMLAnchorElement>
> = ({ children, href, ...props }) => {
return null
}
We’re typing this specifically with the HTMLAnchorElement
type to ensure that anchor properties, like href
are accepted by TypeScript. We’re extracting children
and href
so we can separate them from the other props, then we’re gathering the remaining props with the rest operator so we can pass them through to the underlying element.
Now we’ll update the render with a couple ternaries. Let’s lead with the code, then talk through it.
export const SmartLink: FunctionComponent<
DetailedHTMLProps<AnchorHTMLAttributes<HTMLAnchorElement>, HTMLAnchorElement>
> = ({ children, href, ...props }) => {
return href.startsWith('http') ? (
<a href={href} {...props} target="_blank" rel="noreferrer">
{children}
</a>
) : href.startsWith('#') ? (
<a href={href} {...props}>
{children}
</a>
) : (
<Link href={href}>
<a {...props}>{children}</a>
</Link>
)
}
First we check for the href
to start with http
. If there is an http
or https
at the start of the address, we’ll return a standard anchor with a target="_blank"
attribute and a rel="noreferrer"
.
If we’re not dealing with an external link, we then check for an href that starts with a hash. In that case, we’ll return a standard anchor since this is a link to an anchor on the same page.
Finally, if it’s not external or a link to an anchor on the same page, it should be a link to another page within our app. In this case, we use the Link
component to allow a client side transition.
Replace Default Link Handling with SmartLink
With that component in place, we need to put it to work for our blog posts. We’ll import the SmartLink
component into pages/blog/[slug].tsx
. And we’ll map the a
element to the SmartLink
in the components
object.
import type { GetStaticPaths, GetStaticProps } from 'next'
import Head from 'next/head'
import { getAllPostSlugs, getPostBySlug } from '../../utils'
import { MDXRemoteSerializeResult } from 'next-mdx-remote/dist/types'
import { MDXRemote } from 'next-mdx-remote'
import { HotTip } from '../../components/HotTip'
+import { SmartLink } from '../../components/SmartLink'
const components = {
HotTip,
strong: ({ children }) => (
<strong className="text-orange-500 font-bold">{children}</strong>
),
+ a: SmartLink,
}
With that in place, loading up the /blog/hello-world
route should render out our 3 links. The external link should launch a new tab, the internal link should jump to the heading, and the local link should navigate with a client transition rather than a full page load.
Updating Styles for External Links
For external links, it would improve the user experience to indicate that a link will open in a new window. Since we’re rendering external links differently from the others inside SmartLink
, we’ll just go back there and add some extra styling.
export const SmartLink: FunctionComponent<
DetailedHTMLProps<AnchorHTMLAttributes<HTMLAnchorElement>, HTMLAnchorElement>
> = ({ children, href, ...props }) => {
return href.startsWith('http') ? (
- <a href={href} {...props} target="_blank" rel="noreferrer">
+ <a
+ href={href}
+ {...props}
+ target="_blank"
+ rel="noreferrer"
+ className="after:content-['\2197'] after:text-orange-300"
+ >
{children}
</a>
) : href.startsWith('#') ? (
<a href={href} {...props}>
{children}
</a>
) : (
<Link href={href}>
<a {...props}>{children}</a>
</Link>
)
}
We’re using the after
variant here to add an arrow that points up and to the right with unicode.
Conclusion
Now we have all the benefits of Next’s routing when we need it, support standard browser behavior for anchors, and launch a new tab and provide context clues about link behaviors for external links, all without having to deviate from markdown when creating content.
Check back next week for the next article in the series, Optimize Images in Your MDX Posts with the Next Image Component!