Can't resolve 'react-dom/server' in Sanity studio schemas folder

44 replies
Last updated: Jun 8, 2020
How do i solve:
Module not found: Error: Can't resolve 'react-dom/server'
• i have react-dom installed in package.json
Seems it tries to find it in:

studio/schemas/objects
AI Update

I can help you with this "Module not found: Can't resolve 'react-dom/server'" error in your Sanity Studio schemas. This is a known issue that occurs because Sanity Studio's build process doesn't include server-side modules by default, even though you have react-dom installed.

Based on a troubleshooting thread from the Sanity community, here's the solution:

The Workaround

Instead of importing react-dom/server using the standard package import, use a relative path to your node_modules folder:

// Instead of this:
import ReactDOMServer from "react-dom/server";

// Use this:
import ReactDOMServer from '../../node_modules/react-dom/server'

You'll need to adjust the relative path (../../) based on where your schema file is located. For example:

  • If your file is in studio/schemas/objects/, use ../../../node_modules/react-dom/server
  • If it's in studio/schemas/, use ../../node_modules/react-dom/server

Why This Happens

Sanity Studio's webpack configuration doesn't automatically resolve server-side React modules like react-dom/server because the Studio runs in the browser. The build process excludes these server-specific modules to keep the bundle size down and avoid compatibility issues.

Alternative Consideration

Before implementing this workaround, consider whether you actually need server-side rendering in your Studio schema. The use case in the linked thread was trying to convert React icons to SVG strings for storage. Depending on your needs, you might want to:

  1. Store just the icon name/identifier instead of the full SVG markup
  2. Render the SVG on your frontend when displaying the content
  3. Use a different approach that doesn't require renderToString() in the Studio

This keeps your Studio schemas simpler and avoids the module resolution issue entirely. But if you do need the server-side rendering capability, the relative path import should resolve your error.

Show original thread
44 replies
How are you importing it at the moment?
import ReactDOMServer from "react-dom/server";
As stated here:
And it’s in your
package.json
under
dependencies
or
devDependencies
for example?
"react-dom": "^16.13.1",
under dependencies
Could you try removing your
node_modules
folder and
package-lock.json
file before running
npm install
to see if that resolves it?
Also, what Node version are you using (
node -v
)?
v12.16.3
It works on my website using React, so it must be something specific to Sanity
Updated to latest node version, no change
Here is the entire error if that helps:
Have you tried the above procedure as well with your
node_modules
folder and the
package-lock.json
file?
yes
Tried using both Npm and Yarn
Alright, thanks for confirming and testing with Yarn as well. I’ll try to reproduce and see if we have a workaround 🙂
Thank you (Removed Name) 🙂
Do you want me to send you the code?
No need, thanks - was able to reproduce. We’re looking it 🙂
Awesome 🙂
Thanks for the quick responses
Could you say a bit more about your use case for ReactDOMServer? Maybe there’s a workaround while this is not yet resolved?
I’m currently exporting icon names using icon-picker in Sanity, but this means i have to load all SVG icons on my website, which causes bloat.
Therefore i want it to just send the SVG code of the react-icon instead of just the name
this:
We’ll work on resolving the issue, but to have a workaround for now, I just managed to get this to function with a relative path instead:
import ReactDOMServer from '../node_modules/react-dom/server'
[You’d have to adapt this a bit depending on where you’re importing from]
Thank you, i will try that 🙂
I’m trying to do this:
{iconsArray.icons.map((icon) => {
    const Icon = ReactIcons[icon];
    const rendered = ReactDOMServer.renderToString(Icon());

    return (
        <>
            <span className="btn" onClick={() => clearSearch()}>
                <button
                    style={{
                        background: "transparent",
                        borderRadius: "0.1rem",
                        margin: "0.1rem",
                    }}
                    onClick={() =>
                        onChange(createPatchFrom(rendered))
                    }
                >
                    <Icon />
                </button>
            </span>
        </>
    );
})}
And loading like this in schema:

{
    title: "Velg et hovedikon",
    description: "Søk etter ikoner:",
    name: "icon",
    type: "string",
    inputComponent: IconPicker,
},
But getting this error:
I get it to render the SVG code as a string in Sanity, but i cannot manage to get it to set it as a value:
What does your
createPatchFrom
look like for the custom input component?
(or the entire
IconPicker
component)
// The patch function that sets data on the document
const createPatchFrom = (value) =>
    PatchEvent.from(value === "" ? unset() : set(String(value)));
Entire code looks like this:
import React from "react";
import ReactDOMServer from "../../node_modules/react-dom/server";
import PatchEvent, { set, unset } from "part:@sanity/form-builder/patch-event";
import FormField from "part:@sanity/components/formfields/default";

import * as ai from "react-icons/ai";
import * as bs from "react-icons/bs";
import * as di from "react-icons/di";
import * as fa from "react-icons/fa";
import * as fc from "react-icons/fc";
import * as fi from "react-icons/fi";
import * as gi from "react-icons/gi";
import * as go from "react-icons/go";
import * as gr from "react-icons/gr";
import * as io from "react-icons/io";
import * as md from "react-icons/md";
import * as ri from "react-icons/ri";
import * as ti from "react-icons/ti";
import * as wi from "react-icons/wi";
const ReactIcons = {
    ...ai,
    ...bs,
    ...di,
    ...fa,
    ...fc,
    ...fi,
    ...gi,
    ...go,
    ...gr,
    ...io,
    ...md,
    ...ri,
    ...ti,
    ...wi,
};

// The patch function that sets data on the document
const createPatchFrom = (value) =>
    PatchEvent.from(value === "" ? unset() : set(String(value)));

const IconPicker = (props) => {
    const { type, value = undefined, onChange } = props;
    const [iconsArray, setIconsArray] = React.useState({ icons: [] });
    const [pickerValue, setPickerValue] = React.useState([]);
    const [searchQuery, setSearchQuery] = React.useState();

    const handleChange = (query) => {
        setPickerValue(query);
        const arr = [];
        Object.keys(ReactIcons).map((go) => {
            if (
                go.toLowerCase().includes(query.toLowerCase()) &&
                query.length > 2
            ) {
                arr.push(go);
                setIconsArray({ icons: arr });
            }
        });
    };

    // If deleting searchquery, clear icons
    React.useEffect(() => {
        if (pickerValue.length < 2) {
            setIconsArray({ icons: [] });
        }
    }, [pickerValue]);

    // On icon select
    const getIcon = (icon) => {
        const Icon = ReactIcons[icon];
        return (
            <>
                <Icon />
            </>
        );
    };

    const clearSearch = () => {
        setPickerValue([]);
        setSearchQuery("");
    };
    return (
        <>
            <FormField
                label={type.title}
                description={
                    value !== undefined ? "Valgt ikon: " : type.description
                }
            >
                {value !== undefined ? (
                    <div style={{ position: "relative" }}>
                        <p style={{ fontSize: "2rem" }}>
                            {getIcon(value)} <b>{value}</b>
                        </p>
                        <button
                            style={{
                                fontSize: "1.5rem",
                                position: "absolute",
                                top: 0,
                                right: 0,
                                background: "transparent",
                                color: "red",
                                border: 0,
                            }}
                            onClick={() => onChange(createPatchFrom(""))}
                        >
                            {getIcon("AiFillCloseCircle")}
                        </button>
                    </div>
                ) : (
                    ""
                )}
                <input
                    type="text"
                    onChange={(event) => handleChange(event.target.value)}
                    className="DefaultTextInput_input_2wVzD text-input_textInput_31n9_ text-input_root_1xAqy"
                />
                {iconsArray.icons.map((icon) => {
                    const Icon = ReactIcons[icon];
                    const iconRendered = ReactDOMServer.renderToString(Icon());

                    return (
                        <>
                            <span className="btn" onClick={() => clearSearch()}>
                                <button
                                    style={{
                                        background: "transparent",
                                        borderRadius: "0.1rem",
                                        margin: "0.1rem",
                                    }}
                                    // Working:
                                    // onClick={() =>
                                    //     onChange(createPatchFrom(icon))
                                    // }
                                    // Not working
                                    onClick={() =>
                                        onChange(createPatchFrom(iconRendered))
                                    }
                                >
                                    <Icon />
                                </button>
                            </span>
                        </>
                    );
                })}
            </FormField>
        </>
    );
};

export default IconPicker;
Any idea what causes this?
createPatchFrom
expects a
String
value in this case, which it is likely not receiving from where
createPatchFrom
is called - with
icon
yes, but with
iconRendered
no.
This might be surprising, given that you use
renderToString()
to generate
iconRendered
. Could you try to console log the type of
iconRendered
to confirm it’s actually a string?
It says string
Do you think patchEvent gets the value before it is rendered to string somehow?
Would it be possible to use a promise or something?
What happens if you change the patch function to:
const createPatchFrom = value => PatchEvent.from(value === '' ? unset() : set(value))
Also, what value is logged here, simply
undefined
?
i will check
same error
And if you change
renderedIcon
to
icon
it still works with the original patch function?
In this earlier reply, if you change the icon to a different one, does it successfully update the SVG output directly?
https://sanity-io-land.slack.com/archives/C9Z7RC3V1/p1591620484416700?thread_ts=1591608917.403700&amp;cid=C9Z7RC3V1
I can output it as string and JSX underneath the search as you can see
Could you DM me a zip of your schema file for this plus the custom input component? Happy to have a look later today if I can spot anything 🙂
Of course 🙂 Incredible service
(Resolved in DM - rendering issue with
getIcon()
in the end, patch was perfect!) 🚀

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?