Mukhtar Bahadory

Properly Handling Next.js Navigation with Markdown Pages

If you've ever wanted to create a blog, you've probably been told to use Markdown and I agree that you should.

However, in the context of Next.js there are a few cases to watch out for.

For example, local navigation should be done via the Link component as it is highly optimized. External navigation should be done via opening up new target window for the client and it's also very important to support anchor links to jump to the various headings on a page. The last case is extremely important in the case of a table of contents.

The following component is such that it is able to handle all those three cases.

import Link from 'next/link';

export default function CustomLink(props) {
  const { children, href } = props
  console.log(props, 'props inside customLink')
  if (href.startsWith('/') || href === '') {
    console.log('actual Link component')
    return (
      <Link href={href} passHref={true}>
        <a>
          {children}
        </a>
      </Link>
    )
  } else if (href.startsWith('#')) {
    return (
      <a
        href={href}
        aria-hidden="true"
        tabindex="-1"
      >
        {children}
      </a>
    )

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

If it's an internal link, it will render a standard Next.js Link component. If it's an anchor jump, it will render the appropriate anchor element and likewise in the case of an external link.

Obviously, you'll still need a library that lets you replace anchor elements of Markdown with the aforementioned component. Using the Unified, Remark, and Rehype ecosystem I was able to create the following component. You simply have to pass it Markdown.

import React from 'react';

import CustomLink from './CustomLink';



import rehypeSlug from 'rehype-slug';
import rehypeAutoLinkHeadings from 'rehype-autolink-headings';

import unified from 'unified'
import markdown from 'remark-parse'
import remark2rehype from 'remark-rehype'
import rehype2react from 'rehype-react'


// ...
var processor = unified()
  .use(markdown)
  .use(remark2rehype)
  .use(rehypeSlug)
  .use(rehypeAutoLinkHeadings)
  .use(rehype2react, {
    createElement: React.createElement,
    components: {
      a: CustomLink
    }
  })

const PrintMarkdown = ({ markdown }) => {


  return (
    <div>
      {processor.processSync(markdown).result}
    </div>
  );
}

export default PrintMarkdown;