Ingap.dev - A Personal Dev Blog

It is better to be approximately right than precisely wrong. (Warren Buffet)

Published on Saturday 11 May 2024

Tags: nextjs-135 markdown1

Syntax Highlighting for Markdown in Next.js 13 (The Right Way)

Enhance your Next.js 13 project with this detailed guide on implementing PrismJS for dynamic syntax highlighting in Markdown-rendered content.


In this tutorial, we'll explore how to add syntax highlighting to Markdown-rendered content in a Next.js 13 application. Whether you're building a blog, documentation, or any site that features code snippets, syntax highlighting is essential for readability. We'll use PrismJS for syntax highlighting and markdown-to-jsx to convert Markdown into JSX.

Syntax highlighting is a feature used in text editors and web pages that displays source code in different colors and fonts according to the category of terms. This not only enhances the aesthetic appeal but also increases the readability and understanding of the code. PrismJS, our choice for this tutorial, is a popular syntax highlighting library because it's lightweight, easy to use, and supports a wide range of languages. By integrating PrismJS into your Next.js project, you can significantly improve how code snippets are visualized in your Markdown content.

Prerequisites

Before we begin, ensure you have the following:

  • Node.js installed on your system.
  • A basic understanding of Next.js and React.
  • An existing Next.js 13 project. If you don't have one, create one using npx create-next-app.
  • I assume you are using markdown-to-jsx to convert Markdown into JSX components. This package allows us to integrate Markdown content seamlessly within our React components. If not, I recommend reading its npm page.

Step 1: Install Required Packages

First, install markdown-to-jsx and prismjs:

npm install prismjs markdown-to-jsx @types/prismjs @types/markdown-to-jsx

This command will not only install markdown-to-jsx and prismjs, but also their TypeScript definitions (@types/prismjs and @types/markdown-to-jsx).

If you want to know why I like to use Typescript, read this.

Step 2: Create the CodeBlock Component

The CodeBlock component will use PrismJS to apply syntax highlighting. Create CodeBlock.tsx:

// CodeBlock.tsx
import React from 'react';
import Prism from 'prismjs';
import 'prismjs/components/prism-typescript';  // Ensure TypeScript support

interface CodeBlockProps {
  language?: string;
  children: React.ReactNode;  // More accurate typing for React children
}

const CodeBlock: React.FC<CodeBlockProps> = ({ language = 'typescript', children }) => {
  // Extract string from ReactNode if it's a code element
  const codeString = typeof children === 'string' ? children : React.Children.toArray(children).reduce<string>((acc, child) => {
    if (React.isValidElement(child) && typeof child.props.children === 'string') {
      return acc + child.props.children;
    }
    return acc;
  }, '');

  // Apply syntax highlighting
  const html = Prism.highlight(codeString, Prism.languages[language] || Prism.languages.plaintext, language);

  return (
    <pre className={`language-${language}`}>
      <code className={`language-${language}`} dangerouslySetInnerHTML={{ __html: html }} />
    </pre>
  );
};

export default CodeBlock;

It uses Prism.highlight, a function provided by PrismJS, to transform plain text code into formatted HTML by applying CSS styles based on the language specified. The component dynamically extracts the code string from its children, applies highlighting, and safely injects the resulting HTML using React's dangerouslySetInnerHTML to preserve the integrity of the syntax styling.

Step 3: Include PrismJS Theme in Your Application

You need to pass these options

interface OverrideProps {
  children?: React.ReactNode;
  className?: string;
}
  
const markdownOptions = {
  overrides: {
    pre: {  // Assuming all code blocks are wrapped in `pre` tags in Markdown
      component: CodeBlock,
      props: ({ children, className = '' }: OverrideProps) => {
        // Extract the language from the className
        const match = /language-(\w+)/.exec(className);
        const language = match ? match[1] : 'plaintext';  // Default to 'plaintext' if no language is detected
  
        return { language, children };
      }
    }
  },
};

To the Markdown element, so I had to turn this

//...
<Markdown>{post.content}</Markdown>
//...

into this

//...
<Markdown options={markdownOptions}>{post.content}</Markdown>
//...

Step 4: Include PrismJS Theme in Your Application

Ensure that the PrismJS theme CSS is globally available across your application by importing it. In my case, I've included it in the 'page.css' file, which is located at the same level as 'page.tsx'. This is because I'm not utilizing the 'pages' directory.

@import 'prismjs/themes/prism-tomorrow.css';

Pick up any other one among default styles:

@import 'prismjs/themes/prism-dark.css';
@import 'prismjs/themes/prism-funky.css'; 
@import 'prismjs/themes/prism.css'; 
@import 'prismjs/themes/prism-okaidia.css';

Or manually download the CSS from this GitHub Repository and make it available in node_modules\prismjs\themes.

Conclusion

You now have a fully functional setup for rendering Markdown with syntax highlighting in your Next.js 13 application. This setup enhances the readability of code snippets within your Markdown content, making them clearer and more engaging for your readers.