Issue with using JSDOM in htmlToBlocks function for server-side API

3 replies
Last updated: Jul 12, 2023
Hi All,I am using htmlToBlocks function to convert my html data from quill text editor to blocks in order to upload them to sanity. It was working perfectly in client side but I wanted to move it to serverside api. In order to do it, I have added JSDOM as parseHtml option. I put below code together but keep getting error '- error TypeError: Cannot read properties of undefined (reading 'firstElementChild')' it does not even go into deserialize since I do not see my log for deserialize. I am using Nextjs 13. Also I know it is not related to JSDOM because it works perfectly outside htmlToBlocks.
I have tried JSDOM(html).window.document instead of JSDOM(html) but this raise another error. "- error ReferenceError: window is not defined" after looping thru all of the elements of the html
Any ideas?


const convertedBlocks = htmlToBlocks(content, blockContentType, {
                parseHtml: (html) => new JSDOM(html),
                rules: [
                    // Special rule for code blocks
                    {
                        deserialize(el, next, block) {
                            console.log('el: ', el);
                            if (el.tagName?.toLowerCase() === 'img') {
                                const src = el.getAttribute('src');
                                const alt = el.getAttribute('alt');
                                console.log('el: ', el);
                                // Create block object for each image
                                const imageBlock = {
                                    _type: 'image',
                                    asset: {
                                        _type: 'reference',
                                        _ref: 'toReplace' + index
                                    }
                                    // Add any other relevant metadata fields
                                };
                                imageLibrary.push(src);
                                index++;
                                return block(imageBlock);
                            }
                            // Continue with default conversion for other elements
                            return undefined;
                        }
                    }
                ]
            });
Error for JSDOM(html).window.document


- error ReferenceError: window is not defined
    at Module.getRandomValues (webpack-internal:///(sc_server)/./node_modules/get-random-values-esm/index.mjs:7:5)
    at whatwgRNG (webpack-internal:///(sc_server)/./node_modules/@sanity/block-tools/lib/index.js:185:37)
    at randomKey (webpack-internal:///(sc_server)/./node_modules/@sanity/block-tools/lib/index.js:193:12)
    at normalizeBlock (webpack-internal:///(sc_server)/./node_modules/@sanity/block-tools/lib/index.js:1145:15)
    at eval (webpack-internal:///(sc_server)/./node_modules/@sanity/block-tools/lib/index.js:1201:56)
    at Array.map (<anonymous>)
    at htmlToBlocks (webpack-internal:///(sc_server)/./node_modules/@sanity/block-tools/lib/index.js:1201:43)
    at eval (webpack-internal:///(sc_server)/./lib/sanityDatabaseFunctions.js:55:102)
    at new Promise (<anonymous>)
    at convertHtmlToBlocks (webpack-internal:///(sc_server)/./lib/sanityDatabaseFunctions.js:49:12)
    at eval (webpack-internal:///(sc_server)/./lib/sanityDatabaseFunctions.js:110:34)
    at new Promise (<anonymous>)
    at writeNewSolutionData (webpack-internal:///(sc_server)/./lib/sanityDatabaseFunctions.js:105:12)
    at POST (webpack-internal:///(sc_server)/./src/app/api/submit-design/route.js:13:110)
    at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
    at async eval (webpack-internal:///(sc_server)/./node_modules/next/dist/server/future/route-modules/app-route/module.js:244:37)
Error for JSDOM(html)


- error TypeError: Cannot read properties of undefined (reading 'firstElementChild')
    at preprocessGDocs (webpack-internal:///(sc_server)/./node_modules/@sanity/block-tools/lib/index.js:426:18)
    at eval (webpack-internal:///(sc_server)/./node_modules/@sanity/block-tools/lib/index.js:463:9)
    at Array.forEach (<anonymous>)
    at preprocess (webpack-internal:///(sc_server)/./node_modules/@sanity/block-tools/lib/index.js:462:19)
    at HtmlDeserializer.parseHtml (webpack-internal:///(sc_server)/./node_modules/@sanity/block-tools/lib/index.js:1131:25)
    at HtmlDeserializer.deserialize (webpack-internal:///(sc_server)/./node_modules/@sanity/block-tools/lib/index.js:951:30)
    at htmlToBlocks (webpack-internal:///(sc_server)/./node_modules/@sanity/block-tools/lib/index.js:1201:25)
    at eval (webpack-internal:///(sc_server)/./lib/sanityDatabaseFunctions.js:55:102)
    at new Promise (<anonymous>)
    at convertHtmlToBlocks (webpack-internal:///(sc_server)/./lib/sanityDatabaseFunctions.js:49:12)
    at eval (webpack-internal:///(sc_server)/./lib/sanityDatabaseFunctions.js:109:34)
    at new Promise (<anonymous>)
    at writeNewSolutionData (webpack-internal:///(sc_server)/./lib/sanityDatabaseFunctions.js:104:12)
    at POST (webpack-internal:///(sc_server)/./src/app/api/submit-design/route.js:13:110)
    at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
    at async eval (webpack-internal:///(sc_server)/./node_modules/next/dist/server/future/route-modules/app-route/module.js:244:37)
AI Update

Based on the issue you're experiencing, the problem stems from @sanity/block-tools using get-random-values-esm which expects browser globals like window that don't exist in Node.js server environments. This is a known issue when using htmlToBlocks in Next.js 13 server-side API routes.

The solution that worked for others is to add a package override to your package.json:

{
  "overrides": {
    "get-random-values-esm": "npm:get-random-values@^1.2.2"
  }
}

If you're using pnpm, use the pnpm.overrides field instead:

{
  "pnpm": {
    "overrides": {
      "get-random-values-esm": "npm:get-random-values@^1.2.2"
    }
  }
}

Or for Yarn, use resolutions:

{
  "resolutions": {
    "get-random-values-esm": "npm:get-random-values@^1.2.2"
  }
}

After adding this override, delete your node_modules folder and lock file, then reinstall your dependencies:

rm -rf node_modules package-lock.json
npm install

Why this works: Next.js resolves get-random-values-esm to the ESM version which references window.crypto. The override forces it to use the older CommonJS version (get-random-values) which properly detects the Node.js environment and uses Node's crypto module instead.

Also make sure your parseHtml option returns the document object:

parseHtml: (html) => new JSDOM(html).window.document

This was a common pain point with @sanity/block-tools in server-side environments, and the package override is the most reliable workaround until the underlying dependency is updated.

Please don’t create multiple threads when you’re already getting help in one.
You’ll want to look up the differences between client side and server side and why you don’t have access to the DOM on the server side.
I was not sure if all community would see my question to you on the other thread. I understand why I do not have access to the DOM on the server side but I do not understand why it does not work when I provide JSDOM as parseHtml option. It should parse the html in JSDOM and use it's DOM for sub functions. Am I missing something? By the way JSDOM works outside htmlToBlocks function. When I use jsdom(html).Window.document as parseHtml(both options are in documentation weirdly), it processes my html but stuck at normalizeBlock-&gt; getRandomValues called by htmlToBlocks
Thank you very much for your help on this. I have finalized my project and shared it in i-made-thisSolved the above issue with this
https://github.com/sanity-io/sanity/issues/4595#issuecomment-1604050213

Sanity – Build the way you think, not the way your CMS thinks

Sanity is the developer-first content operating system that gives you complete control. Schema-as-code, GROQ queries, and real-time APIs mean no more workarounds or waiting for deployments. Free to start, scale as you grow.

Was this answer helpful?