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:

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:

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:

"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:

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:

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:

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.

Get Started With Sentry

Get actionable, code-level insights to resolve Next.js performance bottlenecks and errors.

Run the line of code below to:

  1. Create a free Sentry account

  2. Run the CLI install command to automatically add the Sentry SDK to your project:

    npx @sentry/wizard@latest -i nextjs
  3. Start capturing errors and performance issues

Loved by over 4 million developers and more than 90,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.

Share on Twitter
Bookmark this page
Ask a questionJoin the discussion

Related Answers

A better experience for your users. An easier life for your developers.

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