[go: nahoru, domu]

Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Unable to import react-dom/server in a server component #43810

Open
1 task done
janus-reith opened this issue Dec 7, 2022 · 11 comments
Open
1 task done

Unable to import react-dom/server in a server component #43810

janus-reith opened this issue Dec 7, 2022 · 11 comments
Labels
bug Issue was opened via the bug report template.

Comments

@janus-reith
Copy link

Verify canary release

  • I verified that the issue exists in the latest Next.js canary release

Provide environment information

Operating System:
Platform: darwin
Arch: arm64
Version: Darwin Kernel Version 22.1.0: Sun Oct 9 20:14:30 PDT 2022; root:xnu-8792.41.9~2/RELEASE_ARM64_T8103
Binaries:
Node: 16.18.0
npm: 8.19.2
Yarn: 1.22.19
pnpm: 7.13.6
Relevant packages:
next: 13.0.7-canary.1
eslint-config-next: N/A
react: 18.2.0
react-dom: 18.2.0

Which area(s) of Next.js are affected? (leave empty if unsure)

App directory (appDir: true)

Link to the code that reproduces this issue

https://stackblitz.com/edit/vercel-next-js-arwqsz?file=app%2Fpage.js

To Reproduce

app/page.js:

import ReactDOMServer from "react-dom/server";

const staticMarkup = ReactDOMServer.renderToStaticMarkup(<p>TEST</p>);

export default function TestPage() {
  return <p>123</p>;
}

Run next dev and try opening the index page.

Describe the Bug

I'm receiving an error message:

Failed to compile.
./app/page.js

You're importing a component that imports react-dom/server. To fix it, render or return the content directly as a Server Component instead for perf and security.

Maybe one of these should be marked as a client entry "use client":
app/page.js

Expected Behavior

Be able to use react-dom/server on the server.
My usecase it to take some React tree, render it so markup, run it through another postprocessing step which is not aware of anything React specific, and then, return an iframe containing that html.

Contrary to what the error message says, I think there's nothing to fix about importing react-dom/server, and neither should any of this require using the "use client" directive

Which browser are you using? (if relevant)

No response

How are you deploying your application? (if relevant)

No response

@janus-reith janus-reith added the bug Issue was opened via the bug report template. label Dec 7, 2022
@juanferreras
Copy link
juanferreras commented Dec 7, 2022

It's similar to what https://github.com/FormidableLabs/react-ssr-prepass and others like https://github.com/kmoskwiak/useSSE rely on (e.g. a real or simulated pre-render pass to hoist certain work and then actually send it already digested into SSR as initial props)

In general, this kind of pattern should become even more of an edge-case, as with RSC you could fetch/do async stuff anywhere and Next.js would attempt to deduplicate the requests.

However, if you do need to render something static (as your example shows, but not pre-rendering your own app itself), you might be able to work around it by dynamically importing ReactDOMServer (see forked stackblitz).

// app/precompile.js
const getData = async (component) => {
  const ReactDOMServer = (await import('react-dom/server')).default;
  const staticMarkup = ReactDOMServer.renderToStaticMarkup(component);
  return staticMarkup;
};

export default getData;

// app/page.js
const STATIC_COMPONENT = <p>Static Component</p>;

export default async function TestPage() {
  // this works OK
  const prerenderStaticComponent = await getData(STATIC_COMPONENT);

  // this does not work (error)
  /**
   * Error: Objects are not valid as a React child (found: object with keys {$$typeof, filepath, name, async}). * If you meant to render a collection of children, use an array instead.
   */
  // const prerenderAClientComponent = await getData(PageWrapperClient);

  // this does not work (warning)
  /**
   * Warning: Functions are not valid as a React child. This may happen if you return a Component instead of <Component /> from render. Or maybe you meant to call this function rather than return it.
   */
  // const prerenderAServerComponent = await getData(PageWrapperServer);

  return (
    <PageWrapperClient>
      <h3>This is a page with static markup</h3>
      <div dangerouslySetInnerHTML={{ __html: prerenderStaticComponent }} />
    </PageWrapperClient>
  );
}

But trying to render your actual tree of components will likely fail as they'll be client or server components and both cases will error out (not quite sure how Next.js internally is handling this to be able to render these new trees, if there's any webpack work involved and why it doesn't just work on its own)

@zoul
Copy link
zoul commented Mar 9, 2023

Same problem here. My use case is rendering a page with various components into a static RSS feed using ReactDOMServer.renderToStaticMarkup.

@errnesto
Copy link
errnesto commented Mar 9, 2023

I found this while trying to render a react component svg in a route.tsxhandler:

export async function GET(req: NextRequest): Promise<NextResponse> {
  const ReactDOMServer = (await import('react-dom/server')).default
  const component = <ReactComponent />
  const svg = Buffer.from(ReactDOMServer.renderToString(component))
  const png = await sharp(svg).flatten({ background: 'white' }).png().toBuffer()

  return new NextResponse(png, {
    status: 200,
    headers: { 'Content-Type': 'image/png' },
  })
}

Importing ReactDOMServer dynamically does fix this. So thank you – just wanted to share my usecase :-)

@CapitaineToinon
Copy link

Thanks, dynamic import worked for me! My use case is to convert mdx to text to prepare my content for meilisearch, I think that use case is legitimate. Had to do something like this:

export async function toMeilisearch(markdown: string | undefined) {
	const { renderToString } = await import('react-dom/server')

	// from @mdx-js/mdx
	const content = await _evaluate(markdown ?? '', {
		...runtime,
		Fragment,
		remarkPlugins: [remarkGfm, remarkFrontmatter],
		development: false,
	})

	const rendered = renderToString(createElement(content.default))
	
	// from html-to-text
	return convert(rendered, {
		selectors: [
			{
				selector: 'a',
				options: {
					ignoreHref: true,
					uppercaseHeaderCells: false,
				},
			},
		],
	})
}

@tomer-tgp
Copy link

I was able to import

import ReactDOMServer from "react-dom/server.browser";

and use it on the server

@tlchatt
Copy link
tlchatt commented Oct 6, 2023

Same problem building a sitemap.xml with dynamic urls imported from a database is my use case.

@JacobWeisenburger
Copy link

I have the same problem

#57631

@chemiadel
Copy link
chemiadel commented Jan 28, 2024

Here's my solution to render jsx to string on both client and server on NextJS > 13 without having server component issue in import

// render-client.js

import ReactDom from "next/dist/compiled/react-dom/cjs/react-dom-server-legacy.browser.production";
export function renderToStringClient(element) {
  return ReactDom.renderToString(element);
}

// render-server.ts

export function renderToStringServer(element: any) {
  const ReactDOMServer = require("react-dom/server");
  const html = ReactDOMServer.renderToString(element);

  return html;
} 

// index.tsx

import { renderToStringClient } from "./render-client";

export function renderToString(JSXElement: JSX.Element) {
  const isServer = typeof window === "undefined";
  if (isServer) {
    const { renderToStringServer } = require("./render-server");
    // do your server stuff here
    return renderToStringServer(JSXElement);
  }
  // do your client stuff here
  return renderToStringClient(JSXElement);
}

@0-don
Copy link
0-don commented Feb 14, 2024

chemiadel

wow

"use client";
// @ts-ignore
import ReactDom from "next/dist/compiled/react-dom/cjs/react-dom-server-legacy.browser.production";
import { SiRootme } from "react-icons/si";

export default function TestPage({}) {
  const html = ReactDom.renderToStaticMarkup(<SiRootme />);

  const json = JSON.stringify(["SiRootme", html]);

  console.log({ icon: json });
  return <></>;
}

@abiriadev
Copy link

I found this while trying to render a react component svg in a route.tsxhandler:

export async function GET(req: NextRequest): Promise<NextResponse> {
  const ReactDOMServer = (await import('react-dom/server')).default
  const component = <ReactComponent />
  const svg = Buffer.from(ReactDOMServer.renderToString(component))
  const png = await sharp(svg).flatten({ background: 'white' }).png().toBuffer()

  return new NextResponse(png, {
    status: 200,
    headers: { 'Content-Type': 'image/png' },
  })
}

Importing ReactDOMServer dynamically does fix this. So thank you – just wanted to share my usecase :-)

It does not work now: fails with the following message: Error: react-dom/server is not supported in React Server Components.

@Daisymond
Copy link
Daisymond commented Jun 26, 2024

I found this while trying to render a react component svg in a route.tsxhandler:

export async function GET(req: NextRequest): Promise<NextResponse> {
  const ReactDOMServer = (await import('react-dom/server')).default
  const component = <ReactComponent />
  const svg = Buffer.from(ReactDOMServer.renderToString(component))
  const png = await sharp(svg).flatten({ background: 'white' }).png().toBuffer()

  return new NextResponse(png, {
    status: 200,
    headers: { 'Content-Type': 'image/png' },
  })
}

Importing ReactDOMServer dynamically does fix this. So thank you – just wanted to share my usecase :-)

This solution from @abiriadev worked for me.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Issue was opened via the bug report template.
Projects
None yet
Development

No branches or pull requests