DEV Community

Cover image for Make Better Blog Posts with Beautiful Syntax Highlighting in Nextjs with React-Prism-Render
Peter
Peter

Posted on • Originally published at peterlunch.com

Make Better Blog Posts with Beautiful Syntax Highlighting in Nextjs with React-Prism-Render

As I was creating my blog with Next.js, I knew that I wanted to custom style my code blocks as coding was a large part of this blog, among other things.

If you have a Nextjs blog (or any React Framework blog) and want to create beautiful code blocks out of your MDX posts, then this post will show you how to do that using prism-react-renderer.

In this guide, we will see how to convert plain MDX code blocks into stylish ones you'd see in a code editor like VSCode or atom. We are going to see how to do it using Styled Components, but it is also possible to do it with vanilla CSS.

With that in mind, we need to first understand how MDX handles our code blocks, so we can then take that and make it stylish.

Understanding MDX code blocks

Before we look at how to style our code blocks it is helpful to know how MDX formats them.

When I talk about a code block in MDX, what I am talking about is the code you put between the triple back-ticks.

The way MDX code blocks are formatted by the browser is they are wrapped in a pre block and then each line is split into a div. Then, each word or symbol is split into spans. This is how the styler will apply individual styles to each word or symbol. It is important to understand this, because we will need to know which HTML elements to target if we want to style our code blocks with the correct syntax highlighting.

Now, we understand how code is converted from markdown to HTML we are ready to create our component that will make our code blocks more stylish.

Creating the prism react render Syntax Highlighter Component

The first step towards making our Nextjs blogs syntax highlights prettier is by utilizing the prism-react-render package.

The first thing you need to do is install the package.

# npm
npm install --save prism-react-renderer

# yarn
yarn add prism-react-renderer
Enter fullscreen mode Exit fullscreen mode

With that out of the way, we can now build our syntax highlighting component SyntaxHighlighter. The below code is a basic version of the component, copy the code below and we can go through what it is doing.

import Highlight, { defaultProps } from "prism-react-renderer";

const SyntaxHighlighter = ({ children }) => {
  const code = children.props.children;

  return (
    <Highlight {...defaultProps} code={code}>
      {({ className, style, tokens, getLineProps, getTokenProps }) => (
        <pre className={className} style={{ ...style }}>
          {tokens.slice(0, -1).map((line, i) => (
            <div {...getLineProps({ line, key: i })}>
              {line.map((token, key) => (
                <span {...getTokenProps({ token, key })} />
              ))}
            </div>
          ))}
        </pre>
      )}
    </Highlight>
  );
};

export default SyntaxHighlighter;
Enter fullscreen mode Exit fullscreen mode

Above, we start by importing two things from prism-react-renderer. The first is the <Highlight /> component, it styles our code blocks in our MDX files. The second is the { defaultProps } object, this gets spread into the Highlight component and will provide us with some default theming.

Next, we create our SyntaxHighlighter component and pass it a children prop.

Then, we declare the const code and access the mdx code through our children prop. It stores the MDX code block so we can then pass it into the <Highlight /> component.

Inside our Highlight component we create an anonymous function with the props className, style, tokens, getLineProps, getTokenProps.

Within this function we target the pre block. First, we slice all lines and pass them into a div element. Within the div we are going to put each word and token into span elements. Essentially, what happens here is the getLineProps & getTokenProps apply things like the styling to your syntax.

If this was all we did our syntax highlights would now look like the below.

unstyled code block

The reason it looks this way is because defaultProps uses the duotoneDark theme as a default. We will see how to customize themes later.

In the meantime, we can make it look much more stylish by picking out one of the many available themes react-prism-render has built in.

As soon as you have a theme picked out, we can add it to our syntax highlights by importing the theme from import theme from "prism-react-renderer/themes/themeName"; and adding the theme prop to our Highlight component's props.

import Highlight, { defaultProps } from "prism-react-renderer";
import theme from "prism-react-renderer/themes/nightOwlLight";

const SyntaxHighlighter = ({ children }) => {
  const code = children.props.children;

    return (
        <Highlight
      {...defaultProps}
      code={code}
            theme={theme}
     >

    // ...
Enter fullscreen mode Exit fullscreen mode

Now your syntax highlights are looking great. But, what if you want to style how the actual code block looks. What if you want to add things like the language or border-radius? Well, let's see how to do that now with styled-components.

Styling prism-react-render Code Blocks With Styled Components

I'm going to use styled-components to show you how to style your syntax highlights. This method can transfer to any other framework of styling, I just love using styled-components with Nextjs.

To style the block surrounding the code, we need to target the pre block which, we will call PreBlock. Before we do that, we need to wrap the pre block in a div called CodeBlockContainer. The SyntaxHighlighter should now look like the code below.

const SyntaxHighlighter= ({ children }) => {
  //...

  <CodeBlockContainer>
    <PreBlock className={className}>
      {tokens.slice(0, -1).map((line, i) => (
        <div {...getLineProps({ line, key: i })}>
          {line.map((token, key) => (
            <span {...getTokenProps({ token, key })} />
          ))}
        </div>
      ))}
    </PreBlock>
  </CodeBlockContainer>;

  //...
};
Enter fullscreen mode Exit fullscreen mode

Looking at the changes above, we have renamed the pre block and wrapped it in a CodeBlockContainer, this allows us to add some styling to the code blocks.

const CodeBlockContainer = styled.div`
  position: relative;
  margin-top: 48px;
  margin-bottom: 60px;
  transition: all 200ms ease-in 0s;
`;

const PreBlock = styled.pre`
  font-family: Arial, Helvetica, sans-serif;
  font-size: 18px;
  outline-offset: 2px;
  overflow-x: auto;
  margin-left: -32px;
  margin-right: -32px;
  padding: 32px;
  min-height: 50px;
  border: 1px solid rgba(230, 230, 230, 1);
  border-bottom-left-radius: 6px;
  border-bottom-right-radius: 6px;
  max-width: calc(100% + 64px);
`;
Enter fullscreen mode Exit fullscreen mode

This will make your code snippets look like the below.

semi formatted codeblock

That is all there is to styling your code block containers. The key is to target the pre block.

On the whole, your syntax highlighting for your blog would already be looking great with just the above. But, we can take it up a level by adding things like the language or line highlights.

So let's look at how to add the language to your react-prism-render syntax highlights.

Adding Language to prism-react-render

If you look back up at how code is written in markdown you will see a little js next to the three backticks. That tells markdown that the language is JavaScript, you could use CSS or HTML if the code was written in those languages. In fact there is a whole list of languages you can use.

To add language, we need to get the language value you have in your markdown and save it as a variable. Thankfully prism-react-render adds the language as a class name.

Therefore we can access it through the children prop we pass our SyntaxHighlighter component like so children_.props.className?.replace("language-", "").trim();. You will need to save the value of this expression in a const and then pass the Highlighter a language prop.

The prism-react-render syntax highlighter should now look like the below.

const SyntaxHighlighter = ({ children }) => {
  const code = children.props.children;
  const language = children.props.className?.replace("language-", "").trim();

  return (
    <Highlight {...defaultProps} code={code} language={language}>
      {({ className, style, tokens, getLineProps, getTokenProps }) => (
        <CodeSnippetContainer>
          <PreBlock className={className} style={{ ...style }}>
            {tokens.slice(0, -1).map((line, i) => (
              <div {...getLineProps({ line, key: i })}>
                {line.map((token, key) => (
                  <span {...getTokenProps({ token, key })} />
                ))}
              </div>
            ))}
          </PreBlock>
        </CodeSnippetContainer>
      )}
    </Highlight>
  );
};

export default CodeBlock;
Enter fullscreen mode Exit fullscreen mode

The last thing we need to do is render the language variable. To do this we add a LanguageHeadingContainer inside of the CodeSnippetContainer.

const Syntaxhighligher //...

<CodeSnippetContainer>
  {language && (
    <LanguageHeadingContainer>{language.toUpperCase()}</LanguageHeadingContainer>
  )}
    <PreBlock> //...

Enter fullscreen mode Exit fullscreen mode

Above, we use short circuit logic to only render the LanguageHeadingContainer if language is present in our markdown. Next, we need to add the styling for the LanguageHeadingContainer.

const CodeBlockWrapper = styled.div`
  border-top-left-radius: 0.25rem;
  border-top-right-radius: 0.25rem;
  border-width: 1px 1px 0px;
  border-style: solid;
  border-color: rgba(230, 230, 230, 1);
  background-color: rgb(231, 232, 235);
  padding: 0.75rem 1.25rem;
  margin-left: -32px;
  margin-right: -32px;
  font-family: font-family: Arial, Helvetica, sans-serif;;
  font-size: 0.875rem;
  line-height: 1.25rem;
  font-weight: 700;
  color: hsl(220deg, 23%, 5%);
  text-align: right;
`;
Enter fullscreen mode Exit fullscreen mode

After that, your syntax highlights should look like the below.

fully formatted and styled code block

The next step is to ensure we can use our SyntaxHighlighter component with our blog. This component will work with other popular MDX libraries but, I am going to show you how we do it with mdx-bundler.

Using prism-react-render With Mdx-bundler and Nextjs

The next step is to ensure that MDX knows to render the component. This ensures you don't have to do something like the below every time you have some code in your MDX file you want to render.

import SyntaxHighlight from "components/syntaxHighlighter";

<SyntaxHighlighter
  children={```

js
const codeBlock = () => {
    // does something
}


```}
/>;
Enter fullscreen mode Exit fullscreen mode

To manually avoid having to wrap each of your code blocks with the SyntaxHighlighter we will automatically convert them using mdx-bundler.

If you're not familiar with mdx-bundler I have a beginners guide to get you set up.

If you have MDX bundler set up with Nextjs all we need to do is add the SyntaxHighlighter component to the mdx-bundler <Component /> arguments. You will need to import the SyntaxHighlighter component into your [slug].js file.

// [slug].js

<Component
  components={{
    pre: SyntaxHighlighter,
  }}
/>
Enter fullscreen mode Exit fullscreen mode

Above, we have told mdx-bundler to use our SyntaxHighligther component whenever it sees a pre block.

That is all there is to using mdx-bundler with your prism-react-render syntax highlighter component. As a result, you now have stylish syntax highlighting for your code blocks. But, before you go there are two more awesome things I want to show you.

Creating Custom prism-react-render Themes

One of the best parts about using prism-react-render is you can create your own themes. The benefit is you can have a theme that matches your website's design. Let's look at how to create prism-react-render themes now.

Making your own custom theme is similar to how you would make a VSCode theme. So to build your own theme you need to follow a JSON-based format like the below.

var myCustomTheme = {
  plain: {
    color: "#d6deeb",
    backgroundColor: "#011627",
    fontFamily: "var(--font-family-syntax)",
    fontSize: "16px",
  },
  styles: [
    {
      types: ["changed"],
      style: {
        color: "rgb(162, 191, 252)",
        fontStyle: "italic",
      },
    },
    {
      types: ["deleted"],
      style: {
        color: "rgba(239, 83, 80, 0.56)",
        fontStyle: "italic",
      },
    },
    {
      types: ["inserted", "attr-name"],
      style: {
        color: "rgb(173, 219, 103)",
        fontStyle: "italic",
      },
    },
    {
      types: ["comment"],
      style: {
        color: "rgb(99, 119, 119)",
        fontStyle: "italic",
      },
    },
    {
      types: ["string", "url"],
      style: {
        color: "rgb(173, 219, 103)",
      },
    },
    {
      types: ["variable"],
      style: {
        color: "rgb(214, 222, 235)",
      },
    },
    {
      types: ["number"],
      style: {
        color: "rgb(247, 140, 108)",
      },
    },
    {
      types: ["builtin", "char", "constant", "function"],
      style: {
        color: "rgb(130, 170, 255)",
      },
    },
    {
      // This was manually added after the auto-generation
      // so that punctuations are not italicised
      types: ["punctuation"],
      style: {
        color: "rgb(199, 146, 234)",
      },
    },
    {
      types: ["selector", "doctype"],
      style: {
        color: "rgb(199, 146, 234)",
        fontStyle: "italic",
      },
    },
    {
      types: ["class-name"],
      style: {
        color: "rgb(255, 203, 139)",
      },
    },
    {
      types: ["tag", "operator", "keyword"],
      style: {
        color: "rgb(127, 219, 202)",
      },
    },
    {
      types: ["boolean"],
      style: {
        color: "rgb(255, 88, 116)",
      },
    },
    {
      types: ["property"],
      style: {
        color: "rgb(128, 203, 196)",
      },
    },
    {
      types: ["namespace"],
      style: {
        color: "rgb(178, 204, 214)",
      },
    },
  ],
};

export default myCustomTheme;
Enter fullscreen mode Exit fullscreen mode

All you need to do is copy the above code template and paste it into a mycustomTheme.js file that you can then import into the SyntaxHighlighter component. Once you've imported it, you just need to pass myCustomTheme as an argument in the Highligther's theme prop.

  <Highlight
      {...defaultProps}
      code={code}
      language={language}
      theme={myCustomTheme}
  >
Enter fullscreen mode Exit fullscreen mode

That is all there is to it. You can change the colors and other values as you want to make your many prism-react-render themes.

Discussion (0)