Adding code syntax highlighting to Next.js Markdown blog

By Priyash Patil | Updated: Oct 21, 2023 12PM IST

This is the follow-up to my previous post about adding ToC support to NextJs blog. To follow along with this post you can check out the source code from this GitHub Revision.

Problem statement

One of the very important feature of any tech blog is code syntax highlighting. Browsers by default doesn't support code highlighting other than just very basic different font-family.

Solution

Since, I'm using Markdown as my way to write the blogs and have already using unified.js ecosystem to work along with Next.js. Luckily, unified ecosystem already has code highlighting plug-in which uses highlight.js through lowlight.js.

Installing Dependencies

npm install rehype-highlight

Refactoring getPostData and adding CSS

Make following changes to lib/posts.ts

//...
import rehypeSanitize, { defaultSchema } from "rehype-sanitize";
import rehypeHighlight from "rehype-highlight";

export async function getPostData(id: string) {
    //...
    const processedContent = await unified()
        .data("settings", { fragment: true })
        .use(remarkParse)
        .use(remarkToc, { tight: true, ordered: true })
        .use(remarkRehype)
        .use(rehypeHighlight, { subset: false })
        .use(rehypeSanitize, {
        ...defaultSchema,
        attributes: {
            ...defaultSchema.attributes,
            code: [
            ...(defaultSchema.attributes?.code || []),
            // List of all allowed languages:
            [
                "className",
                "hljs",
                "language-js",
                "language-css",
                "language-md",
                "language-shell",
            ],
            ],
            span: [
            ...(defaultSchema.attributes!.span || []),
            [
                "className",
                "hljs-addition",
                "hljs-attr",
                "hljs-attribute",
                "hljs-built_in",
                "hljs-bullet",
                "hljs-char",
                "hljs-code",
                "hljs-comment",
                "hljs-deletion",
                "hljs-doctag",
                "hljs-emphasis",
                "hljs-formula",
                "hljs-keyword",
                "hljs-link",
                "hljs-literal",
                "hljs-meta",
                "hljs-name",
                "hljs-number",
                "hljs-operator",
                "hljs-params",
                "hljs-property",
                "hljs-punctuation",
                "hljs-quote",
                "hljs-regexp",
                "hljs-section",
                "hljs-selector-attr",
                "hljs-selector-class",
                "hljs-selector-id",
                "hljs-selector-pseudo",
                "hljs-selector-tag",
                "hljs-string",
                "hljs-strong",
                "hljs-subst",
                "hljs-symbol",
                "hljs-tag",
                "hljs-template-tag",
                "hljs-template-variable",
                "hljs-title",
                "hljs-type",
                "hljs-variable",
            ],
            ],
        },
        })
        .use(rehypeStringify)
        .use(rehypeSlug)
        .use(rehypeFormat)
        .process(matterResult.content);
    const contentHtml = processedContent.toString();
    //...
}

Add CSS link to components/layout.ts into Head

<link
  rel="stylesheet"
  href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.4.0/styles/monokai-sublime.min.css"
  integrity="sha512-ade8vHOXH67Cm9z/U2vBpckPD1Enhdxl3N05ChXyFx5xikfqggrK4RrEele+VWY/iaZyfk7Bhk6CyZvlh7+5JQ=="
  crossOrigin="anonymous"
  referrerPolicy="no-referrer"
/>

Add following to sytles/global.css

pre code {
  border-radius: 4px;
  padding: 24px !important;
}

code,
code::after,
code::before {
  font-size: 0.9em;
  color: rgb(212, 0, 255);
  font-family: Menlo, Monaco, Lucida Console, Liberation Mono, DejaVu Sans Mono,
    Bitstream Vera Sans Mono, Courier New, monospace, serif;
}

code::after,
code::before {
  content: "`";
}

pre code::after,
pre code::before {
  content: none;
}

Source code

The final source code is on this GitHub Revision. Do note that this source repository is not my actual website. I'll be maintaining a separate source code repository for this blog series.

Conclusion

Syntax highlighting works as expected. One thing to keep in mind that the order of unified use statements is very important. Otherwise, code highlight will not work. We can make some improvement here like, adding support for copying code, but that can be post for another day.