isomorphic-dompurify: Can't resolve 'canvas' on next.js serverless app

What’s the problem

I built a next.js application with serveless option, but simply importing this library it throws this error:

Failed to compile.
ModuleNotFoundError: Module not found: Error: Can't resolve 'canvas' in '/.../node_modules/jsdom/lib/jsdom'
> Build error occurred

I already tried to add canvas library but this doesn’t fix the problem and it throws another error.

Step to reproduce

Add to next.config.js:

module.exports = {
  target: 'serverless'
}

Run next build

Basic Info:

  • “isomorphic-dompurify”: “^0.12.0”,
  • “next”: “^10.0.8”,
  • “react”: “^17.0.1”,
  • “react-dom”: “^17.0.1”

This issue could be related to #35 , but i don’t use umijs.

About this issue

  • Original URL
  • State: open
  • Created 3 years ago
  • Reactions: 8
  • Comments: 39 (15 by maintainers)

Most upvoted comments

Solution

For usage of DOMPurify in Next.js without the wrapper package isomorphic-dompurify, here is a demo of a simple version:

Mainly the code is this (this is a Server Component - if you aren’t using the app/ directory, then do the DOMPurify() call in getServerSideProps or an API Route):

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)//>"
        ),
      }}
    />
  );
}

There is also some required Next.js configuration for this, to resolve the webpack bundling errors:

next.config.js

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

@abedshaaban I found a way to resolve this when using react-pdf in Next Js 13 (app directory)

  1. The best way In your app directory, whatever/page.tsx have this
"use client"
import { pdfjs } from "react-pdf";
pdfjs.GlobalWorkerOptions.workerSrc = 'https://cdnjs.cloudflare.com/ajax/libs/pdf.js/2.16.105/pdf.worker.min.js';

In your next.config.js have this

/** @type {import('next').NextConfig} */
const nextConfig = {
  experimental: {
    appDir: true,
  },
  future: { webpack5: true },
  webpack: (config, { buildId, dev, isServer, defaultLoaders, webpack }) => {
      config.resolve.alias.canvas = false
      config.resolve.alias.encoding = false
      return config
  }
}

module.exports = nextConfig

Let me know if it doesn’t work. Happy to help out. This took me waaaay too long to figure out. Hopefully it will help someone get there faster! 😃

It works now by only adding this in your next.config.js:

module.exports = { reactStrictMode: true, webpack: (config) => { config.externals = […config.externals, “canvas”, “jsdom”]; return config; }, }

and then you can copy this example in your component https://konvajs.org/docs/react/Transformer.html

works fine in : “next”: “^13.4.20-canary.0”

@EnricoNapolitan did you find a solution? I’m encountering something similar with Gatsby.

For me, I’m working with Next.js 13 server component and this worked for me:

Server Component SVGIcon:

import { sanitize } from 'isomorphic-dompurify'

export default async function SVGIcon({
  src,
  width,
  height,
  className,
}: ISVGIcon) {
  const iconResponse = await fetch(`http://localhost:3000${src}`)
  const svgResult = await iconResponse.text()

  return svgResult ? (
    <div
      className={className}
      style={{ width: `${width}px`, height: `${height}px` }}
      dangerouslySetInnerHTML={{ __html: sanitize(svgResult) }}
      aria-hidden={true}
    />
  ) : null
}

next.config.js:

module.exports = {
  reactStrictMode: true,
+ webpack: (config) => {
+   config.externals = [...config.externals, "jsdom"];
+   return config;
+ },
};

Just need to install both isomorphic-dompurify and jsdom, no need to worry about canvas.

I was also having this issue with SvelteKit using the Node adapter. Installing canvas solved the issue, but it would be nice if there were also a solution for serverless.

For usage of DOMPurify in Next.js without the wrapper package isomorphic-dompurify, here is a demo of a simple version:

Mainly the code is this (this is a Server Component - if you aren’t using the app/ directory, then do the DOMPurify() call in getServerSideProps or an API Route):

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)//>"
        ),
      }}
    />
  );
}

There is also some required Next.js configuration for this, to resolve the webpack bundling errors:

next.config.js

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

This works like a charm! Thank you @Daggron

I’ve reproduced the bug locally. Will try to find a solution.

For usage of DOMPurify in Next.js without the wrapper package isomorphic-dompurify, here is a demo of a simple version:

Mainly the code is this (this is a Server Component - if you aren’t using the app/ directory, then do the DOMPurify() call in getServerSideProps or an API Route):

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)//>"
        ),
      }}
    />
  );
}

There is also some required Next.js configuration for this, to resolve the webpack bundling errors:

next.config.js

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

Holy Crap the next config fix worked for me 🥳

@Antonio-Laguna

Usually the error message looks like this:

Failed to compile. ModuleNotFoundError: Module not found: Error: Can’t resolve ‘canvas’ in ‘/…/node_modules/jsdom/lib/jsdom’ Build error occurred

What’s your error message?


As for the server vs client code, modern JS frameworks flirt a lot with the server (SSR, Server Components, etc) and Next is not an exception. Dompurify itself leverages DOM and isomorphic-dompurify utilizes JSDOM to make dompurify work on the server.

Just ran into this in Gatsby, any updates? Also receiving the same “Module parse failed…” error if I try to use this package with canvas installed. I’ll switch to using sanitize-html for now instead.

Just tested on MacOS and building succeeded for me. However, I agree we cant be sure about other environments. There seems to be a reason JSDOM makes canvas optional despite some functionality requiring it

https://github.com/jsdom/jsdom/issues/2603

So if we make it mandatory, we may hit the same issues