react-syntax-highlighter is a React component library that renders syntax-highlighted code blocks using Prism or highlight.js under the hood, and using it safely means never bypassing React's escaping to inject raw highlighted markup from untrusted sources. The library itself is well-behaved by default, but code display is a classic spot where developers reach for dangerouslySetInnerHTML and open an XSS hole. This guide covers correct usage and the specific traps to avoid when the code you render is user-supplied.
Basic, safe usage
Install and use the component in the ordinary way, passing code as a child string and letting the component handle rendering:
import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter';
import { vscDarkPlus } from 'react-syntax-highlighter/dist/esm/styles/prism';
function CodeBlock({ code }) {
return (
<SyntaxHighlighter language="javascript" style={vscDarkPlus}>
{code}
</SyntaxHighlighter>
);
}
Used this way, the library is safe even with untrusted input. It tokenizes the code and renders the tokens as React elements, so React's normal escaping applies and any <script> in the input is displayed as text, not executed. This is the important property: the default path does not use raw HTML injection.
The trap: bypassing React's escaping
The vulnerability people introduce is not in the library, it is in how they wire around it. Two anti-patterns come up repeatedly.
First, taking pre-highlighted HTML from somewhere and injecting it:
// DANGEROUS: never do this with untrusted content
<pre dangerouslySetInnerHTML={{ __html: serverHighlightedCode }} />
If serverHighlightedCode originated from user input and was highlighted server-side without sanitization, any markup in it renders live in the DOM. dangerouslySetInnerHTML disables React's protection by design, which is exactly why it is named that way. If you must render pre-generated HTML, sanitize it first with a library like DOMPurify and treat that as a deliberate, reviewed exception.
Second, the renderer prop and custom code-tag props are powerful and let you build DOM from tokens yourself. That is fine, but if your custom renderer ever concatenates token text into an HTML string and injects it, you have recreated the same hole. Keep custom renderers producing React elements, not HTML strings.
Choosing the build to control bundle size
react-syntax-highlighter ships in a few flavors, and the wrong import balloons your bundle by pulling in every language grammar and theme. Prefer the async/light builds and register only the languages you need:
import { PrismLight as SyntaxHighlighter } from 'react-syntax-highlighter';
import jsx from 'react-syntax-highlighter/dist/esm/languages/prism/jsx';
import { coy } from 'react-syntax-highlighter/dist/esm/styles/prism';
SyntaxHighlighter.registerLanguage('jsx', jsx);
This is not purely a performance point. A smaller, explicit set of grammars is a smaller amount of third-party code executing in your users' browsers, and it makes the dependency easier to reason about. The full build pulls in a large graph of language definitions you will never use.
Dependency hygiene for the package
Like any npm dependency, the library sits on top of others (Prism or highlight.js and their grammar sets), so the supply-chain surface is bigger than the one package name suggests. A few habits keep it manageable:
- Pin the version and commit your lockfile so builds are reproducible and a surprise transitive bump cannot ship silently.
- Run
npm ls react-syntax-highlighterto see what version resolved andnpm auditto check the whole tree against known advisories. - Review the diff when upgrading a major version; the internal highlighting engine and its grammars are where behavior changes hide.
An SCA tool can watch this tree for newly disclosed advisories in the underlying highlighter dependencies, which is where a real issue is more likely to appear than in the thin React wrapper itself. Our SCA overview covers how transitive advisories get surfaced before they reach production.
When the code being displayed is itself untrusted
If you run a product that lets users paste and share code snippets, you are in a higher-risk category and should layer defenses:
- Keep rendering on the safe, React-element path (no
dangerouslySetInnerHTML). - Set a Content Security Policy that disallows inline script execution, so even a mistake does not become code execution.
- If you also let users render Markdown that contains code fences, sanitize the Markdown output; the code fence is safe through this library, but surrounding Markdown-to-HTML conversion is a separate XSS surface.
For a broader treatment of how untrusted content becomes XSS and how to test for it, the XSS prevention fundamentals material walks through the attack class and detection approaches without turning into an exploit cookbook.
FAQ
Is react-syntax-highlighter safe for rendering untrusted code?
Yes, on its default path. It renders code as React elements with normal escaping, so injected markup shows as text rather than executing. It becomes unsafe only if you bypass React with dangerouslySetInnerHTML on unsanitized pre-highlighted HTML.
How do I reduce react-syntax-highlighter's bundle size?
Use the PrismLight or LightAsync builds and register only the languages you actually display with registerLanguage. The default full build imports every grammar and theme, which is far more code than most apps need.
What is the main XSS risk with syntax highlighting in React?
Injecting server-generated highlighted HTML through dangerouslySetInnerHTML without sanitizing it. The highlighter itself is safe; the risk comes from developers rendering raw HTML strings around it. Sanitize with DOMPurify if you must render pre-built HTML.
How should I keep the library's dependencies secure?
Pin the version, commit your lockfile, and run npm audit regularly. The real advisory surface is in the underlying Prism or highlight.js grammars, so use an SCA tool that tracks transitive dependencies rather than only the top-level package.