htmlToBlocks JSDOM parseHtml error in Next.js 13 server-side API

3 replies
Last updated: Jan 28, 2026
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.

Show original thread
3 replies

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?