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:
class="language-javascript"
class=" language-javascript
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.
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.