Sentry Answers>Next.js>

Using dangerouslySetInnerHTML in Next.js

Using dangerouslySetInnerHTML in Next.js

Matthew C.

The Problem

You want to set the inner HTML of an element in a Next.js application. Some use cases for this include:

  • Rendering user content from a rich text editor input field that may include HTML tags.
  • Rendering HTML from a markdown parsing library.
  • Creating a dark mode feature in a server-side rendered app, where the dark mode state is stored in local storage. (You can see an example of how this is done in this blog post: The Quest for the Perfect Dark Mode.)

With vanilla JavaScript, you can use the innerHTML Web API. To set the inner HTML in React, you use the dangerouslySetInnerHTML property, which uses the innerHTML property under the hood. When you render text in React, it sanitizes it by default. It does not sanitize text rendered using dangerouslySetInnerHTML.

To use the dangerouslySetInnerHTML property, pass in an object with a __html key that has a corresponding string value for the HTML string. The React team made the property like this as a safeguard so that developers would look at documentation before using it. It’s not just passing in an HTML string. The HTML string is parsed into HTML elements:

Click to Copy
const rawHTML = "<button>click me</button>"; return <div dangerouslySetInnerHTML={{ __html: rawHTML }} />;

The code above returns an HTML button. As the name suggests, it can be dangerous to set the inner HTML as you may add a XSS vulnerability into your app. This can happen if the HTML comes from content submitted by users or from a third-party source. A script could be added that could access sensitive information or give an attacker unauthorized access to an application. For example:

Click to Copy
const rawHTML = `<img src="" onerror="alert('You have been hacked!');" />`; return <div dangerouslySetInnerHTML={{ __html: rawHTML }} />;

The rawHTML will be rendered, and you’ll see an alert message on your screen that displays: “You have been hacked!“. Note that the <div> tag is self-closing as the element that uses the dangerouslySetInnerHTML property should not have children.

Given the XSS risks, how do you use this property safely?

The Solution

If the HTML string comes from user input or a third-party source, you need to sanitize the input. A popular sanitizer library for HTML is DOMPurify. The library also provides a demo of how DOMPurify works, where you can see how dirty HTML is cleaned by removing dangerous HTML like <script> tags.

When using DOMPurify with Next.js, you need to take server-side rendering into account. From Next.js version 13, components are server-rendered by default. Components are pre-rendered into HTML on the server before being sent to the client. The way you use DOMPurify with Next.js depends on whether you want to use it on the client side only or if you want to use it on the server as well.

Client-side

You sanitize your HTML string with DOMPurify by calling the sanitize method with the HTML string as an argument:

Click to Copy
"use client"; import DOMPurify from "dompurify"; export default function ClientComponent() { const rawHTML = `<img src="" onerror="alert('You have been hacked!');" />`; return <div dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(rawHTML) }} />; }

DOMPurify requires a DOM tree to work. To use it in Next.js, you can use it in a Client Component. Note that Client Components pre-render static HTML on the server. To prevent this pre-rendering, dynamically import the component and set server-side rendering to false so that the import only occurs on the client.

The example code below shows how you can use a wrapper component and dynamic importing to render a client-only component:

Click to Copy
import dynamic from "next/dynamic"; const ClientOnlyComponent = dynamic(() => import("./ClientComponent"), { ssr: false, loading: () => <p>Loading...</p>, }); const ClientOnlyComponentWrapper = () => { return <ClientOnlyComponent />; }; export default ClientOnlyComponentWrapper;

Server-side

If you try to run the DOMPurify sanitize method in a component that also runs on the server, you’ll get the following error:

Click to Copy
Error: dompurify__WEBPACK_IMPORTED_MODULE_1__.default.sanitize is not a function

This error occurs because DOMPurify requires a DOM tree to work. There is no DOM tree in the server-side Node environment.

To use DOMPurify on the server, you can use the jsdom library to create a window object that you can initialize DOMPurify with:

Click to Copy
import { JSDOM } from "jsdom"; const window = new JSDOM("").window; const DOMPurifyServer = DOMPurify(window); const rawHTML = `<img src="" onerror="alert('You have been hacked!');" />`; return <div dangerouslySetInnerHTML={{ __html: DOMPurifyServer.sanitize(rawHTML) }} />;

Alternatively, you can use the isomorphic-dompurify library which allows you to easily use DOMPurify on the client and the server. It uses DOMPurify and jsdom as dependencies to achieve this.

  • Community SeriesDebug Next.js with Sentry
  • ResourcesJavaScript Frontend Error Monitoring 101
  • ResourcesSentry vs. Crashlytics: The Mobile Developer's Decision-Making Guide
  • Syntax.fmListen to the Syntax Podcast
  • Syntax.fm logo
    Listen to the Syntax Podcast

    Tasty treats for web developers brought to you by Sentry. Get tips and tricks from Wes Bos and Scott Tolinski.

    SEE EPISODES

Considered “not bad” by 4 million developers and more than 100,000 organizations worldwide, Sentry provides code-level observability to many of the world’s best-known companies like Disney, Peloton, Cloudflare, Eventbrite, Slack, Supercell, and Rockstar Games. Each month we process billions of exceptions from the most popular products on the internet.

© 2024 • Sentry is a registered Trademark of Functional Software, Inc.