If you cannot render them on the server side, you can do them on the client side but it could be dangerous if the script has malicious content:
Here is a code sample of how you can do it: https://codesandbox.io/s/2rwqy2m0r
import React, { Component, useState, isValidElement } from "react";
import ReactDOM from "react-dom";
const HtmlComponent = ({ html }) => (
isValidElement(html) ? html : <div dangerouslySetInnerHTML={{ __html: html }} />
)
class ScriptComponent extends Component {
componentDidMount() {
eval(this.props.children);
}
render() {
return null;
}
}
const htmlTexts = {
'With h1': {
description: 'Normal visual HTML elements are rendered corretly',
html: '<h1>Test</h1>',
},
'With script': {
description: '<script /> tags in HTML are never executed on client side',
html: '<script>alert(\'test\');</script>',
},
'With style': {
description: '<style /> gets interputed by DOM, and updates element styles',
html: '<style>body {background: red;}</style>',
},
'With script image': {
description: 'element event actions are triggered so XSS is still possible, even without <scripts /> being evaluated',
html: '<img src="foo" onerror="(() => alert(\'foo\'))()" />',
},
'With button': {
description: 'element event actions are triggered so XSS is still possible, even without <scripts /> being evaluated',
html: '<button onclick="(() => alert(\'foo\'))()">Click me</button>',
},
'As script element': {
description: 'Event reacts own script tag doesn\'t evaluate its content',
html: <script>alert('foo')</script>,
},
'As script component': {
description: 'To run scripts on tag load, a component has to activly execute it',
html: <ScriptComponent>alert('foo')</ScriptComponent>,
}
}
function App() {
const [{ html, description }, setHtml] = useState({});
return (
<div className="App">
<h1>dangerouslySetInnerHTML with script</h1>
{Object.keys(htmlTexts).map((name, i) => (
<button
style={{
outline: 'none',
border: 'solid 1px #ddd',
margin: '10px 10px 30px 0',
borderRadius: '8px',
padding: '10px',
boxShadow: html === htmlTexts[name].html ? '0 0 15px rgba(0,0,0,.3)' : 'none'
}}
onClick={() => setHtml(htmlTexts[name])}
>
{name}
</button>
))}
<div>
{description}
</div>
<pre
style={{
background: '#222',
color: '#fff',
padding: '10px',
}}
>
{isValidElement(html) ? (
'React Component\n\n' +
`type: ${typeof html.type === 'string' ? html.type : html.type.name}\n\n` +
`props: ${JSON.stringify(html.props, null, ' ')}`
) : (
`html:\n\n${html}`
)}
</pre>
<HtmlComponent html={html} />
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);