forest
forest

Reputation: 1464

Next.js DOMPurify.sanitize() shows TypeError: dompurify__WEBPACK_IMPORTED_MODULE_6___default.a.sanitize is not a function

I am using DOMPurify.sanitize() inside dangerouslySetInnerHTML={{}} to display innerHtml returned from the database. For initial purpose I'm using getServersideProps() with next-redux-wrapper for this page.

I installed dompurify with: npm i -S dompurify, present version is: "^2.2.6".

My code:

    import DOMPurify from 'dompurify';
    import { useSelector } from 'react-redux';
    import { END } from 'redux-saga';
    import wrapper from '../redux/storeSetup';

    const EmployeePage = () => {
    
        const blog = useSelector(state => state.data);

        const html_body = blog[0].body_html;
    
        const clean = DOMPurify.sanitize(html_body);
    
        return(
           <div className="mainContainer">
                <div dangerouslySetInnerHTML ={{ __html: clean }} />
                <ul>
                    {blog.map((item) => (
                        <li key={item.author}>
                            <span>{item.author}</span><br/>
                            <span>{item.title}</span>
                            <h4>{item.body_delta}</h4>
                            <p>{item.body_html}</p>
                            <p>{item.created_on}</p>
                        </li>
                    ))}
                </ul>
            </div>
        );
    }

    export const getServerSideProps = wrapper.getServerSideProps( async ({store}) => {
        store.dispatch({type: GET_DATA});
        store.dispatch(END);
        await store.sagaTask.toPromise();    
    });
    export default EmployeePage;

But when I'm running this with npm run dev I'm getting this error: TypeError: dompurify__WEBPACK_IMPORTED_MODULE_1___default.a.sanitize is not a function.

What is wrong here? I tried with even simpler codes but everything shows the same error! What am I doing wrong?

Upvotes: 39

Views: 40347

Answers (10)

Sinan A
Sinan A

Reputation: 58

You can use dynamic import for Dompurify used component. Parent component:

const Content = dynamic(() => import('./Content'), {
  ssr: false,
  loading: () => <p>Loading...</p>
});

component file:

'use client';

import DOMPurify from 'dompurify';

const Content: React.FC = () => {
  const example = "<p> hello </p>
  return (
       <div
          dangerouslySetInnerHTML={{
            __html: DOMPurify.sanitize(selectedTask?.content || '', { USE_PROFILES: { html: true } })
          }}
        />
  );
};

export default Content;

Upvotes: 0

volna
volna

Reputation: 2610

Try this:

❌ import * as DOMPurify from 'dompurify';
⭕️ import DOMPurify from 'dompurify';

from: https://github.com/cure53/DOMPurify/issues/526#issuecomment-1435711907

Upvotes: -1

AnasSafi
AnasSafi

Reputation: 6294

dompurify just work on client side component, so you need to add this line "use client"; to top of (tsx,jsx) file, see this full example :

"use client";
import React, { useEffect, useState } from "react";
import DOMPurify from 'dompurify';

export default function MarkdownTextView({text}: { text: string }) {
    const [sanitizedHtml, setSanitizedHtml] = useState("");

    useEffect(() => {
        // This code will be executed only on the client side after the component is mounted
        if (text.length === 0) return;

        const domPurify = DOMPurify(window);
        const cleanHtml = domPurify.sanitize(text); // You might want to pass your markdown-converted HTML here
        setSanitizedHtml(cleanHtml);
    }, [text]); // This effect will re-run if the 'text' prop changes

    if (!sanitizedHtml) return null; // or some placeholder/loading indicator

    return (
        <div dangerouslySetInnerHTML={{__html: sanitizedHtml}}></div>
    );
}

Upvotes: 0

Karl Horky
Karl Horky

Reputation: 4944

Using DOMPurify in Next.js can be accomplished with by installing jsdom and canvas to construct a window object and pass to the DOMPurify() function.

CodeSandbox Demo (uses app directory): https://codesandbox.io/p/sandbox/lingering-river-j5fpi7?file=%2Fapp%2Fpage.tsx

Instructions

First, install the libraries:

npm install jsdom canvas

App Router version (Server Component)

In the app/ directory, you can use React Server Components to run jsdom on the server:

import DOMPurify from 'dompurify';
import { JSDOM } from 'jsdom';

export default function Home() {
  return (
    <div
      dangerouslySetInnerHTML={{
        __html: DOMPurify(new JSDOM('<!DOCTYPE html>').window).sanitize(
          '<img src=x onerror=alert(1)//>'
        ),
      }}
    />
  );
}

Pages Router version

import DOMPurify from 'dompurify';
import { JSDOM } from 'jsdom';

export default function Home(props) {
  return (
    <div
      dangerouslySetInnerHTML={{
        __html: props.sanitizedHtml,
      }}
    />
  );
}

export function getServerSideProps() {
  const sanitizedHtml =
    DOMPurify(new JSDOM('<!DOCTYPE html>').window).sanitize(
      '<img src=x onerror=alert(1)//>'
    );
  return {
    props: {
      sanitizedHtml: sanitizedHtml,
    },
  };
}

In the pages/ directory, you'll also need to configure webpack to mark jsdom and canvas as externals (add the webpack key to your next.config.js file):

/** @type {import('next').NextConfig} */
module.exports = {
  reactStrictMode: true,
  webpack: (config) => {
    config.externals = [...config.externals, 'canvas', 'jsdom'];
    return config;
  },
};

More details here:

https://github.com/vercel/next.js/issues/46893

Upvotes: 1

David Dal Busco
David Dal Busco

Reputation: 8692

Inspired from the two other solutions of this thread - isomorphic-dompurify and the one provided on Github.


  1. In a jest-setup.ts file (provided in the jest.config as setupFilesAfterEnv: ["<rootDir>/jest-setup.ts"]):
const DOMPurify = require("dompurify");
const { JSDOM } = require('jsdom');
const { window } = new JSDOM("<!DOCTYPE html>");
global.DOMPurify = DOMPurify(window);

This will make DOMPurify work with Jest.

  1. In your app code, handles DOMPurify for both window and global sides so that it works both in the browser and in jest / NodeJS contexts. e.g. in TypeScript
import { sanitize as DOMPurifySanitize } from "dompurify";

export const sanitize = (text: string): string => {
  const isBrowser = typeof window !== 'undefined';
  return isBrowser ? DOMPurifySanitize(text) : global.DOMPurify.sanitize(text);
}

Upvotes: 1

Maxime
Maxime

Reputation: 729

I just switch to js-xss and made a component like that :

import React from "react";
import xss from "xss";

const Html = ({ unsafeHtml }: {unsafeHtml: string}): JSX.Element => {
  return (
    <div
      {...props}
      dangerouslySetInnerHTML={{
        __html: xss(unsafeHtml),
      }}
    />
  );
};

Upvotes: 8

GavinB
GavinB

Reputation: 595

Ran into this yesterday with a TypeScript/React set up.

import DOMPurify from 'dompurify';

export const sanitize = (html: string): string => DOMPurify.sanitize(html);

This code worked perfectly inside Jest tests but failed in the Browser as the DOMPurify object was undefined. Upon digging I found that there was a DOMPurify object attached at the window scope. I wound up having to put a hack inplace to handle the differing behavior between running in node and in the browser:

import DOMPurify from 'dompurify';

interface IDOMPurifyWindow extends Window {
    DOMPurify: typeof DOMPurify;
}
const purify =
    ((window as unknown) as IDOMPurifyWindow)?.DOMPurify || DOMPurify;

export const sanitize = (html: string): string => DOMPurify.sanitize(html);

There is a compiler warning but it's functional. I could eliminate the compiler warning by importing the es.js version of the library directly: import DOMPurify from 'dompurify/dist/purify.es.js'; But that resulted in Jest being unable to run as it needs vanilla JavaScript.

This library does great things, but it's not super friendly to use if you're using TypeScript and running in a browser.

Upvotes: 1

Mark O
Mark O

Reputation: 1167

Use Isomorphic dompurify

It can render on server side and browser

Upvotes: 57

forest
forest

Reputation: 1464

I found a working solution: instead of sanitizing the innerHtml on the server side, I had to sanitize it on the client side right after submitting the rich-text blog, in my case, react-quill:

import dynamic from 'next/dynamic'
import {useState} from 'react'
import DOMPurify from 'dompurify';

const QuillNoSSRWrapper = dynamic(import('react-quill'), {
    ssr: false,
    loading: () => <p>Loading...</p>,
})

// quill modules definitions
//...

export default function articles() {
    const [text, setText] = useState(preData);

    function handleTextChange(content, delta, source, editor) {
        //let str = JSON.stringify(editor.getContents());
        //let parsed = JSON.parse(str)
        setText(editor.getContents());
        const cleaned = DOMPurify.sanitize(editor.getHTML());
        console.log('Cleaned Html: ', cleaned);

    return (
        <div className="quill_container">
            <div id="editor" className="editor">
                <QuillNoSSRWrapper
                    id="quilleditor"
                    modules={modules}
                    formats={formats}
                    theme="snow"
                    value={text}
                    onChange={handleTextChange}
                  />
             </div>
        </div>
    );
};

Upvotes: 1

Or Assayag
Or Assayag

Reputation: 6346

According to this: https://github.com/cure53/DOMPurify/issues/29#issuecomment-466626162

Try the follow (from the example above):

import { JSDOM } from 'jsdom'
import DOMPurify from 'dompurify'
const { window } = new JSDOM(html_body);
const domPurify = DOMPurify(window);
console.log(domPurify.sanitize(html_body));

Upvotes: 0

Related Questions