2

I am trying to implement server-side rendering of syntax highlighted (tokenized) code blocks using Prismjs (note: I know how to do this using client-side rendering via useEffect and refs, and I got this working using prism-react-renderer. I am looking specifically for the solution for "bare" Prismjs & SSR).

// codeExamples = array of some code strings

const Code = ({ code, language }) => {
  const html = Prism.highlight(code, Prism.languages[language], language);
  return (
    <pre
      data-language={language}
      className={`language-${language}`}
    >
      <code
        dangerouslySetInnerHTML={{
          __html: html,
        }}
      />
    </pre>
  );
};

export default function Home() {
  return codeExamples.map((example, i) => (
    <Code
      language="javascript"
      code={example}
      key={i}
    ></Code>
  ));
}

It does work to some extent, but I've run into an issue: the code block gets briefly re-rendered, probably because of a leading whitespace on the class attribute:

  • 1st render: class="language-javascript"
  • 2nd render: class=" language-javascript

enter image description here

This is causing (besides the poinless expensive re-render) a non-pleasant layout shift, temporarily fixed by adding hard-coded font-size in pixels to a <pre> element.

I've got some occasional warnings, either for server and client props mismatch (can't reproduce right now) or for Extra attributes from the server: class – but only when running next dev, not while running next build && next start.

Tested on the latest versions of Nextjs and Prism.

HynekS
  • 2,738
  • 1
  • 19
  • 34

1 Answers1

2

I've got an answer from a dive into Prismjs source code:

if (parent && parent.nodeName.toLowerCase() === 'pre') {
    parent.className = parent.className.replace(lang, '').replace(/\s+/g, ' ') + ' language-' + language;
}

This is where the whitespace comes from. It is quite obvious, why it is here (as a padding for string concatenation).

A temporary hacky fix is to add a leading whitespace to a className on <pre> element as well.

<pre
   data-language={language}
   className={` language-${language}`}
>

But it is quite clear that this is not a good way to use Prismjs for SSR.

HynekS
  • 2,738
  • 1
  • 19
  • 34
  • 1
    It's not just with SSR, SSG (Static Site Generation) is also having the problem. Probably anything with rehydration on the client will get this. I also got the case where class names were switched position... I would add Prismjs uses quite a bad way to manage classes. Either you set it with `className` or you add/remove through `classList`. – Jonny Jun 16 '21 at 14:29