code

Generate Blog Heading Anchors in React-Markdown

Add GitHub-style anchor links to your blog headings.

By Amir Ardalan3 min read5 likes

What Are Heading Anchors?

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.

Create Markdown.tsx

In this example, I am passing in the raw markdown to our component with the markdown prop:

tsx
1// Markdown.tsx 2import ReactMarkdown from 'react-markdown' 3 4export default function Markdown({ markdown }) { 5 6 const MarkdownComponents: object = { 7 // Code will go here 8 } 9 10 return ( 11 <ReactMarkdown 12 components={MarkdownComponents} 13 > 14 {markdown.content} 15 </ReactMarkdown> 16 ) 17 18} 19 20

Create generateSlug.ts in utils Folder

We will use this to generate the anchor slug.

typescript
1// generateSlug.ts 2 3 4const generateSlug = (str: string) => { 5 6 str = str?.replace(/^\s+|\s+$/g, '') 7 str = str?.toLowerCase() 8 const from = 'àáãäâèéëêìíïîòóöôùúüûñç·/_,:;' 9 const to = 'aaaaaeeeeiiiioooouuuunc------' 10 11 for (let i = 0, l = from.length; i < l; i++) { 12 str = str.replace(new RegExp(from.charAt(i), 'g'), to.charAt(i)) 13 } 14 15 str = str?.replace(/[^a-z0-9 -]/g, '') 16 .replace(/\s+/g, '-') 17 .replace(/-+/g, '-') 18 19 return str 20} 21 22export default generateSlug 23

Import generateSlug.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:

tsx
1// Markdown.tsx 2import ReactMarkdown from 'react-markdown' 3import generateSlug from '@/utils/generateSlug' 4 5const MarkdownComponents: object = { 6 h3: (props: any) => { 7 const arr = props.children 8 let heading = '' 9 10 for (let i = 0; i < arr.length; i++) { 11 if (arr[i]?.type !== undefined) { 12 for (let j = 0; j < arr[i].props.children.length; j++) { 13 heading += arr[i]?.props?.children[0] 14 } 15 } else heading += arr[i] 16 } 17 18 const slug = generateSlug(heading) 19 return <h3 id={slug}><a href={`#${slug}`} {...props}></a></h3> 20 } 21} 22... 23

Here we are targeting the h3 headings in the markdown and looping through the returned node arrays. The for loop 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.

Final Thoughts

You 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.

Did you enjoy this post?

amirardalan.eth QR Code

amirardalan.eth

ETH address copied to clipboard ✅