Learn by doing - Sariyanta

How to add syntax highlighter to HubSpot blog post with Rehype Pretty Code with Rehype

Written by Sariyanta | Dec 22, 2024 2:23:49 PM

Implementing a syntax highlighter in a blog enhances readability and user experience by visually distinguishing code elements, making complex snippets easier to understand. It gives your blog a polished, professional appearance, facilitates learning for readers, and ensures consistent formatting across posts. Additionally, it boosts engagement and accessibility, attracting a broader audience and potentially improving SEO.

The Process

The tools that I'm familiar with that works in React are mostly built to work with markdown. Since this blog is written in HubSpot CMS Blog, the output is html. Because of this, I have to convert the html first to markdown and then adds syntax highlighting and finally convert it back to html. 

Implementation on HubSpot CMS React

There's not really anything much different in the implementation in HubSpot CMS React compare to any other react project. If you have Professional subscription and up, you have serverSideProps available which allows the whole process to run on the server. But if you have a free account like I do, then you have to do the processing client side. I like how the code highlighting looks on the shadcn website, and for that I need to install these packages

npm i @rehype-pretty/transformers rehype-parse rehype-pretty-code rehype-remark rehype-stringify remark-parse remark-rehype remark-stringify unified

Initial solution with serverSideProps

import {
  ModulePropsWithoutSSP,
  withModuleProps,
} from '@hubspot/cms-components';
import { transformerCopyButton } from '@rehype-pretty/transformers';
import rehypeParse from 'rehype-parse';
import rehypePrettyCode from 'rehype-pretty-code';
import rehypeRemark from 'rehype-remark';
import rehypeStringify from 'rehype-stringify';
import remarkParse from 'remark-parse';
import remarkRehype from 'remark-rehype';
import remarkStringify from 'remark-stringify';
import { unified } from 'unified';

const parseHTML = async (props: ModulePropsWithoutSSP) => {
  const markdown = await unified()
    .use(rehypeParse)
    .use(rehypeRemark)
    .use(remarkStringify)
    .process(props.hublData.body);

  const body = await unified()
    .use(remarkParse)
    .use(remarkRehype)
    .use(rehypePrettyCode, {
      defaultLang: 'ts',
      grid: true,
      theme: 'github-dark',
      transformers: [
        transformerCopyButton({
          visibility: 'always',
          feedbackDuration: 3_000,
        }),
      ],
    })
    .use(rehypeStringify)
    .process(markdown);

  return {
    serverSideProps: {
      body: String(body),
    },
    caching: {
      cacheControl: {
        maxAge: 60,
      },
    },
  };
};

export const getServerSideProps = withModuleProps(parseHTML);

Poorman's solution  

import { transformerCopyButton } from '@rehype-pretty/transformers';
import { useEffect, useState } from 'react';
import rehypeParse from 'rehype-parse';
import rehypePrettyCode from 'rehype-pretty-code';
import rehypeRemark from 'rehype-remark';
import rehypeStringify from 'rehype-stringify';
import remarkParse from 'remark-parse';
import remarkRehype from 'remark-rehype';
import remarkStringify from 'remark-stringify';
import { unified } from 'unified';

export const useRehypePrettyCode = (content: string) => {
  const [html, setHtml] = useState(null);

  useEffect(() => {
    (async () => {
      const markdown = await unified()
        .use(rehypeParse)
        .use(rehypeRemark)
        .use(remarkStringify)
        .process(content);

      const body = await unified()
        .use(remarkParse)
        .use(remarkRehype)
        .use(rehypePrettyCode, {
          defaultLang: 'ts',
          grid: true,
          theme: 'github-dark',
          transformers: [
            transformerCopyButton({
              visibility: 'always',
              feedbackDuration: 3_000,
            }),
          ],
        })
        .use(rehypeStringify)
        .process(markdown);

      setHtml(String(body));
    })();
  }, [html]);

  return html;
};