4 min read
— views•Generate Blog Heading Anchors in React-Markdown
If you are not already familiar with heading anchors, check out any README.md
on GitHub or hover over a heading within most blog posts and you will likely see a small link icon or hashtag to the left of the title, indicating that you can click to anchor to that specific heading.
If you'd like to link someone to a specific part of a blog post, you can simply click the title you want to send them to and the address bar is automatically populated with an anchor that will link directly to that section.
#Why Not Download an Existing Package?There are excellent rehype and remark plugins such as remark-autolink-headings and rehype-slug. If your goal is to get this feature working as quickly as possible, those are great options.
The issue arises when you do this for every little feature in your blog. If you are loading in 5-10+ plugins into react-markdown
you are going to be shipping enormous javascript bundles to the client.
Extending react-markdown
is super simple and your visitors will feel the difference in first contentful paint times.
Markdown.tsx
In this example, I am passing in the raw markdown to our component with the markdown
prop:
// Markdown.tsx
import ReactMarkdown from 'react-markdown';
export default function Markdown({ markdown }) {
const MarkdownComponents: object = {
// Code will go here
}
return (
<ReactMarkdown
components={MarkdownComponents}
>
{markdown.content}
</ReactMarkdown>
)
};
tsxgenerateSlug.ts
in utils
Folder
We will use this to generate the anchor slug.
// generateSlug.ts
const generateSlug = (str: string) => {
str = str?.replace(/^\s+|\s+$/g, '');
str = str?.toLowerCase();
const from = 'àáãäâèéëêìíïîòóöôùúüûñç·/_,:;';
const to = 'aaaaaeeeeiiiioooouuuunc------';
for (let i = 0, l = from.length; i < l; i++) {
str = str.replace(new RegExp(from.charAt(i), 'g'), to.charAt(i));
}
str = str
?.replace(/[^a-z0-9 -]/g, '')
.replace(/\s+/g, '-')
.replace(/-+/g, '-');
return str;
};
export default generateSlug;
typescriptgenerateSlug.ts
and Customize the Heading Node
In my blog, I am using the H3
heading for blog post headings. Change this to whatever fits your needs:
// Markdown.tsximport ReactMarkdown from 'react-markdown';import generateSlug from '@/utils/generateSlug';h3: (props: H3Props) => { const children = Array.isArray(props.children) ? props.children : [props.children]; const heading = children .flatMap((element) => typeof element === 'string' ? element : element?.type !== undefined && typeof element.props.children === 'string' ? element.props.children : [] ) .join(''); const slug = generateSlug(heading); return ( <h3 id={slug}> <a href={`#${slug}`} {...props}></a> </h3> );},...
tsxHere we are targeting the h3
headings in the markdown and iterating through the returned node arrays via a flatMap
method. The flatMap will look for anything inside the title that isn't a normal string, such as a code
object.
We initially create an empty string assigned to the variable heading
and concatenate any code objects with the strings. Once we have a heading string, we pass it to the generateSlug
function, and finally we tell the ReactMarkdown
component to output an h3 element with an anchor inside of it that contains our newly generated slug.
You may want to add a scrolling animation to your page when a user clicks an inbound anchor link or clicks on one of the headings. This helps orient the user to the action that has occured (the page is moving to bring an anchor into view). The following CSS should do the trick:
html {
scroll-behavior: smooth;
}
scssYou can wrap the markdown component in a class to target with CSS or pass a css prop directly to the ReactMarkdown
component. From there it's up to you to style your link/hashtag icon on hover to indicate your titles are now anchors.
Enjoy this post? Like and share!