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

renderToReadableStream not found in react-dom/server #868

Closed
garybaines opened this issue Jul 27, 2023 · 63 comments · Fixed by #1009
Closed

renderToReadableStream not found in react-dom/server #868

garybaines opened this issue Jul 27, 2023 · 63 comments · Fixed by #1009
Labels
Type: Bug Confirmed bug

Comments

@garybaines
Copy link

garybaines commented Jul 27, 2023

Describe the Bug

We are testing out react-email for use in our project, but getting this error when trying to deploying a test template:

yarn start
yarn run v1.22.19
$ serverless offline --httpPort 4051 --stage local --host 0.0.0.0
serverless-offline-ssm checking serverless version 3.33.0.
✖ in ../node_modules/@react-email/components/node_modules/@react-email/render/dist/index.mjs 31:23-45
    export 'renderToReadableStream' (imported as 'renderToReadableStream') was not found in 'react-dom/server' (possible exports: renderToNodeStream, renderToPipeableStream, renderToStaticMarkup, renderToStaticNodeStream, renderToString, version)

Template code:

import React from 'react'
import {
  Body,
  Head,
  Heading,
  Container,
  Html,
  Text,
} from '@react-email/components'

interface AccessRequestParams {
  testName: string
}

export const accessRequestExpiryTemplate = ({
  testName,
}: AccessRequestParams) => (
  <Html>
    <Head />
    <Body>
      <Container>
        <Heading>TEST</Heading>
        <Text>Here are the details of the request:</Text>
        <Text>{testName}</Text>
      </Container>
    </Body>
  </Html>
)

export default accessRequestExpiryTemplate

Package.json versions

    "dependencies": {
    "@react-email/components": "^0.0.7",
    "react-email": "^1.9.4",
    "react": "18.2.0",
    "react-dom": "^18.2.0",
    "react-router-dom": "^6.11.2",
    "react-tooltip": "4.2.21",
  }

Which package is affected (leave empty if unsure)

@react-email/components

Link to the code that reproduces this issue

n/a

To Reproduce

Deploy with the listed package versions -
We're using serverless: '"start": "serverless offline --httpPort 4051 --stage local --host 0.0.0.0"'

Expected Behavior

The code should compile cleanly without this import error

What's your node version? (if relevant)

18.13.0

@garybaines garybaines added the Type: Bug Confirmed bug label Jul 27, 2023
@Moicky
Copy link

Moicky commented Aug 3, 2023

Same issue here.

Problem most likely is, that this is only supported on environments which support WebStreams. This makes it impossible to run on some backends.

docs

@Moicky
Copy link

Moicky commented Aug 3, 2023

Maybe this helps someone:

I needed a quick fix since my webpack kept crashing due to this function not being available in my NodeJS environment.

I used NormalModuleReplacementPlugin to provide a fake implementation since this function is not used in my app:

/* webpack.config.js */

const path = require("path");
const { NormalModuleReplacementPlugin } = require("webpack");

module.exports = {
  // ...
  plugins: [
    new NormalModuleReplacementPlugin(
      /email\/render/,
      path.resolve(__dirname, "./src/renderEmailFix.js"),
    ),
  ],
  // ...
};
// @ts-nocheck
const { renderToStaticMarkup } = require("react-dom/server");

module.exports.render = (component) => {
  const doctype =
    '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">';
  const markup = renderToStaticMarkup(component);
  return `${doctype}${markup}`;
};

michael-dickinson-sainsburys added a commit to michael-dickinson-sainsburys/react-email that referenced this issue Sep 6, 2023
@shellscape
Copy link

Here's what the fix for this looks like shellscape/jsx-email@2eeebfc

@Burry
Copy link

Burry commented Oct 20, 2023

For me this issue pops up with the latest Next.js release v13.5.6, which bumped the version of React it uses

@subvertallchris
Copy link

I've been using this happily with Next.js 13.5.6 but it fails immediately on upgrade to Next.js 14.0.0.

@subvertallchris
Copy link

Here's what the fix for this looks like shellscape/jsx-email@2eeebfc

Thank you so much @shellscape! Here is a patch file that implements this for @react-email/render

diff --git a/node_modules/@react-email/render/dist/index.mjs b/node_modules/@react-email/render/dist/index.mjs
index 27cc390..99fd935 100644
--- a/node_modules/@react-email/render/dist/index.mjs
+++ b/node_modules/@react-email/render/dist/index.mjs
@@ -26,9 +26,19 @@ var renderAsPlainText = (component, _options) => {
 // src/renderAsync.ts
 import { convert as convert2 } from "html-to-text";
 import pretty2 from "pretty";
-import { renderToReadableStream, renderToStaticMarkup as renderToStaticMarkup2 } from "react-dom/server";
+
+import react from 'react-dom/server';
+
+const { renderToStaticMarkup, renderToStaticMarkup: rendertoStaticMarkup2 } = react;
+const renderToStream =
+  // Note: only available in platforms that support WebStreams
+  // https://react.dev/reference/react-dom/server/renderToString#alternatives
+  react.renderToReadableStream ||
+  // Note: only available in Node
+  react.renderToPipeableStream;
+
 async function renderToString(children) {
-  const stream = await renderToReadableStream(children);
+  const stream = await renderToStream(children);
   const html = await readableStreamToString(
     stream
   );

@natedunn
Copy link

Can confirm this completely breaks on Next 14.0.0

@shellscape
Copy link

@subvertallchris no prob. fwiw we've completely moved away from using react for rendering, which makes it fully compatible with edge and all fe frameworks. using react for rendering was a big footgun

@TaylorFacen
Copy link

Confirming that react email worked fine until upgrading to next 14.

@JP-HoneyBadger
Copy link

Following, exited to get rid of experimental in next.config.js I updated to 14 and now get ./node_modules/@react-email/render/dist/index.mjs
Attempted import error: 'renderToReadableStream' is not exported from 'react-dom/server' (imported as 'renderToReadableStream').

@DarrylBrooks97
Copy link

Yup this breaks on upgrade to Next 14 🙃

@alexanderbnelson
Copy link

Yes indeed, very much busted on Next 14.

@martinez-hugo
Copy link

So now there are no patch/fix solution for react-email with Next 14 ?

@yannxaver
Copy link

@subvertallchris no prob. fwiw we've completely moved away from using react for rendering, which makes it fully compatible with edge and all fe frameworks. using react for rendering was a big footgun

My experience with https://github.com/shellscape/jsx-email has been good so far. It's easier to set up the preview server and bugs get fixed quickly if you offer a reproduction.

@callumthomson
Copy link

Also unable to get working with NextJS 14

@bricesuazo
Copy link

Is there any progress on this that would allow us to monitor its status?

@themendelson
Copy link

themendelson commented Oct 27, 2023

If anyone is looking for a temporary NextJS 14 fix (using @Moicky's solution)

in next.config.js add these imports:

const path = require("path");
const {
    NormalModuleReplacementPlugin
} = require("webpack");

& add this webpack configuration somewhere in the file:

    webpack: (
        config, {
            buildId,
            dev,
            isServer,
            defaultLoaders,
            nextRuntime,
            webpack
        }
    ) => {
        config.plugins = config.plugins || []
        config.plugins.push(new NormalModuleReplacementPlugin(
            /email\/render/,
            path.resolve(__dirname, "./renderEmailFix.js"),
        ))
        // Important: return the modified config
        return config
    }

Finally, create a new file renderEmailFix.js in the root folder with this code:

// @ts-nocheck
const {
    renderToStaticMarkup
} = require("react-dom/server");

module.exports.render = (component) => {
    const doctype =
        '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">';
    const markup = renderToStaticMarkup(component);
    return `${doctype}${markup}`;
};

@subvertallchris
Copy link

subvertallchris commented Oct 27, 2023 via email

@zenorocha
Copy link
Collaborator

We're currently working on a fix for that. Will keep you all posted.

@thevuong
Copy link

thevuong commented Oct 28, 2023

We're currently working on a fix for that. Will keep you all posted.

The new release doesn't work because in package.json appears workspace:*

https://www.npmjs.com/package/@react-email/components?activeTab=code

@jlison
Copy link

jlison commented Oct 29, 2023

If you're facing difficulties after upgrading to Next.js 14, you may find this workaround useful. I created a new utility function called renderEmailHtml.ts, that follows a very similar approach to render-async

// @utils/renderEmailHtml.ts

import type {ReactElement} from 'react';
import type {ReactDOMServerReadableStream} from 'react-dom/server';

async function readStream(readableStream: ReactDOMServerReadableStream) {
  const reader = readableStream.getReader();
  const chunks: AllowSharedBufferSource[] = [];

  while (true) {
    const {value, done} = await reader.read();
    if (done) {
      break;
    }
    chunks.push(value);
  }

  return chunks.map((chunk) => new TextDecoder('utf-8').decode(chunk)).join('');
}

export async function renderEmailHtml(component: ReactElement) {
  // @see https://github.com/vercel/next.js/issues/43810#issuecomment-1462075524
  // Dynamically import ReactDOMServer to circumvent:
  // "Unable to import react-dom/server in a server component"
  const reactDOMServer = (await import('react-dom/server')).default;
  // Note: only available in platforms that support WebStreams
  // https://react.dev/reference/react-dom/server/renderToString#alternatives
  const renderToStream =
    reactDOMServer.renderToReadableStream ||
    reactDOMServer.renderToPipeableStream;

  const doctype =
    '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">';

  const readableStream = await renderToStream(component);
  const html = await readStream(readableStream);
  html
    // Remove leading doctype becuase we add it manually
    .replace(/^<!DOCTYPE html>/, '')
    // Remove empty comments to match the output of renderToStaticMarkup
    .replace(/<!-- -->/g, '');

  return `${doctype}${html}`;
}

Note
☝️ See how we are dynamically importing react-dom/server
(vercel/next.js#43810 (comment)) to circumvent the error:
"Unable to import react-dom/server in a server component"

Usage in, for example, GET app/api/email/welcome/route.ts

// @app/api/email/welcome/route.ts
import Welcome from '@emails/Welcome';
import {renderEmailHtml} from '@utils/renderEmailHtml';

export const runtime = 'edge';

export async function GET() {
   // ...
  const emailHtml = await renderEmailHtml(
    Welcome({name}),
  );
  // ...
}

That solution worked for me.

@bukinoshita
Copy link
Member

I'm wondering what's your setup, if one can confirm how are you using? I'm testing this way in next.js 14

import { render } from '@react-email/render';
import { MyEmail } from '../../../emails/my-email';
import { NextResponse } from 'next/server';

export const runtime = 'edge'

export const GET = async () => {
  const html = await render(MyEmail());
  return NextResponse.json({ html }, { status: 200 })
}

@darasus
Copy link

darasus commented Oct 30, 2023

@bukinoshita I think using render package in any node/edge environment would cause this. (personally using nodejs).

I think problem also occurs when just installing render package with pnpm.

@devrsi0n
Copy link

devrsi0n commented Oct 30, 2023

I'm using the plain fetch as a work around, here is an example from the official doc:

import { NextResponse } from 'next/server';

export const runtime = 'edge';
export const dynamic = 'force-dynamic';

const RESEND_API_KEY = 're_123456789';

export async function POST() {
  const { renderToStaticMarkup } = await import('react-dom/server');
  const res = await fetch('https://api.resend.com/emails', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'Authorization': `Bearer ${RESEND_API_KEY}`,
    },
    body: JSON.stringify({
      from: 'Acme <onboarding@resend.dev>',
      to: ['delivered@resend.dev'],
      subject: 'hello world',
      html: renderToStaticMarkup(<Email />),
    }),
  });

  if (res.ok) {
    const data = await res.json();
    return NextResponse.json(data);
  }
}

@bukinoshita
Copy link
Member

Can you try using the canary version of render to see if this is still happening?

{
  ...,
  "@react-email/render": "0.0.9-canary.0",
}

@bukinoshita
Copy link
Member

We just released a new canary version @react-email/render@0.0.9-canary.2.

here's an example on Next.js 14 app router edge

import { NextResponse } from 'next/server';
import { EmailTemplate } from '../../../components/email-template';
import { renderAsync } from '@react-email/render';

export const runtime = 'edge';

export async function GET() {
  try {
    const html = await renderAsync(
      EmailTemplate({ firstName: 'John' }) as React.ReactElement
    );
    return NextResponse.json({ html });
  } catch (error) {
    return NextResponse.json({ error });
  }
}

@kgrady13
Copy link

We just released a new canary version @react-email/render@0.0.9-canary.2.

here's an example on Next.js 14 app router edge

import { NextResponse } from 'next/server';
import { EmailTemplate } from '../../../components/email-template';
import { renderAsync } from '@react-email/render';

export const runtime = 'edge';

export async function GET() {
  try {
    const html = await renderAsync(
      EmailTemplate({ firstName: 'John' }) as React.ReactElement
    );
    return NextResponse.json({ html });
  } catch (error) {
    return NextResponse.json({ error });
  }
}

Working for me...appreciate the fast work

@jason-dark
Copy link

+1 for migrating to jsx-email. Painless and worked out of the box in our nx monorepo.

@nnennajohn
Copy link

+1 for jsx-email. Worked on first try. No additional installations, various errors and long debugging. Thanks to those that recommended it. I updated to react-email 9 and resend 2 and still had multiple errors. Decided to give jsx-email a try from all the comments here.

@jorvixsky
Copy link

jorvixsky commented Nov 3, 2023

Just in case the fix from PR #1009 does not work for someone using Next.js 14: I figured out that the last version of @react-email/render is not installing automatically.

You need to go to package.json under the .email folder that is automatically created and upgrade @react-email/render to 0.0.9. Also I upgraded postcss to 8.4.21 (was unmet dependency for the version that is automatically installed, 8.4.19)

I also removed the appDir experimental function from next.config.js (again, under the .email folder) - as according to Next is no longer valid.

With this changes now works perfectly.

@clodal
Copy link

clodal commented Nov 4, 2023

As @bukinoshita mentioned, the fix is in @react-email/render@0.0.9

We'll just need to upgrade your local @react-email/render package from "0.0.7" to "0.0.9".

  • In ./react-starter-email/package.json, pin the @react-email/render version to 0.0.9 using resolutions and also bump the dep versions for good measure:
// ./react-starter-email/package.json
{
  "dependencies": {
    "@react-email/components": "0.0.11",
    "react-email": "1.9.5"
  },
  "resolutions": {
    "@react-email/render": "^0.0.9"
  }
}
  1. Do npm/yarn/pnpm install to apply the change.
  2. Done.

@nicitaacom
Copy link

Thank you - your solution works for me

@pjborowiecki
Copy link

None of the options above worked for me.

I might have found a solution though: installing components separately, rather than using @react-email/components. So I have them in my package.json like this now:

    "@react-email/body": "^0.0.4",
    "@react-email/button": "^0.0.11",
    "@react-email/container": "^0.0.10",
    "@react-email/head": "^0.0.6",
    "@react-email/html": "^0.0.6",
    "@react-email/preview": "^0.0.7",
    "@react-email/section": "^0.0.10",
    "@react-email/tailwind": "^0.0.12",
    "@react-email/text": "^0.0.6",

Everything works great and builds with no errors, even with the latest version of resend (2.0.0). I am using Next 14.0.1. Hope that helps someone! I spent way too much time trying to fix react-email and resend related errors lately

@catalinpit
Copy link

As @bukinoshita mentioned, the fix is in @react-email/render@0.0.9

We'll just need to upgrade your local @react-email/render package from "0.0.7" to "0.0.9".

  • In ./react-starter-email/package.json, pin the @react-email/render version to 0.0.9 using resolutions and also bump the dep versions for good measure:
// ./react-starter-email/package.json
{
  "dependencies": {
    "@react-email/components": "0.0.11",
    "react-email": "1.9.5"
  },
  "resolutions": {
    "@react-email/render": "^0.0.9"
  }
}
  1. Do npm/yarn/pnpm install to apply the change.
  2. Done.

Unfortunately, it doesn't work for me... neither does the above one...

@blakebullis
Copy link

blakebullis commented Nov 6, 2023

I struggled with the initial guidance as well, I did a lot of tinkering, and this is what I currently have that got it working. Unfortunately, I cannot explain why it works now.

"@react-email/components": "^0.0.11" // this broke Button component pY and pX props, you will need to update
"@react-email/render": "^0.0.9-canary.2"
"next": "14.0.2-canary.6"


Resend you mail provider?
"resend": "^2.0.0-canary.2"
if you use Resend: I had to bump this too.

Minor changes are required here around chaining methods. Resend.emails.send() instead of Resend.sendEmail() Also change react: to html: now that you are using renderAsync to generate the HTML of the email.`

@nvegater
Copy link

nvegater commented Nov 6, 2023

I also moved to jsx-email. Way better and is not breaking. Migration took 10 minutes.

@catalinpit
Copy link

"@react-email/render": "^0.0.9-canary.2"

Thanks! This didn't work either... :-(

@issamlk1
Copy link

issamlk1 commented Nov 7, 2023

updating @react-email/render from 0.0.7 to "@react-email/render": "^0.0.9", (with --force)

worked for me,

just to mention @blakebullis comment about the break in pY pX properties in buttons, it also happened to me.
but I think the fix is very easy it's only padding that could be added directly into the style, I guess, let's hope it's not something else and my email didn't got messed up.

@bukinoshita
Copy link
Member

render

If you're still having issues with renderToReadableStream on @react-email/render, you should upgrade the @react-email/render to v0.0.9 (latest).

Eg:

import { renderAsync } from '@react-email/render'

const html = async renderAsync(EmailTemplate({ firstName: 'John' }))

components

If you're still having issues with renderToReadableStream on @react-email/components, you should upgrade the @react-email/components to v0.0.11 (latest).

Eg:

import { renderAsync } from '@react-email/components'

const html = async renderAsync(EmailTemplate({ firstName: 'John' }))

resend

If you're still having issues with renderToReadableStream on resend, you should upgrade the resend to v2.0.0 (latest).

Eg:

const { data, error } = await resend.emails.send({
  from: 'team@example.com',
  to: 'delivered@resend.dev',
  subject: 'Hello!',
  react: EmailTemplate({ firstName: 'John' })
})

Still having issues after upgrading

  • If you're using render or components, please provide a code example and your setup (a reproducible repository works better)
  • If you're using resend, please follow up in the resend-node repository

@catalinpit
Copy link

Okay, the solution was simpler than expected.

Even though I updated package.json with the appropriate version, the package-lock.json contained the older versions, and the app somehow picked those (something something about the node module resolution algorithm).

To fix this, I added the line "@react-email/render": "0.0.9" both under the "dependencies" and "overrides" in the root package.json. It now forces all references to that package to use v0.0.9

Snap

Many thanks to @lxunos for the fix/lesson. 🙏

@gjohnsx
Copy link

gjohnsx commented Nov 8, 2023

Okay, the solution was simpler than expected.

Even though I updated package.json with the appropriate version, the package-lock.json contained the older versions, and the app somehow picked those (something something about the node module resolution algorithm).

To fix this, I added the line "@react-email/render": "0.0.9" both under the "dependencies" and "overrides" in the root package.json. It now forces all references to that package to use v0.0.9

Snap

Many thanks to @lxunos for the fix/lesson. 🙏

This actually worked, thanks! Delete package-lock and node_modules, npm i, and it's working.

@julioDazza
Copy link

Hey Guys, Those who use the Resend library, update to V2.0.0, it works in Next14. https://www.npmjs.com/package/resend

@inventive-username
Copy link

No luck with any of the above packages. What magic are you all using? 😂
Also on next14, with overrides and all.

@catalinpit
Copy link

No luck with any of the above packages. What magic are you all using? 😂 Also on next14, with overrides and all.

Ops, you're in for a rough one then. 👀

@bukinoshita
Copy link
Member

No luck with any of the above packages. What magic are you all using? 😂 Also on next14, with overrides and all.

Is there a reproducible code?

@espressom
Copy link

espressom commented Nov 27, 2023

@catalinpit I was able to resolve the issue as well. Thank you.
Below is the method I applied:

  1. Navigate to the .react-email directory and then delete the package-lock.json and yarn.lock files.
  2. Modify @react-email/render in package.json from "0.0.7" to "0.0.9".
  3. Run yarn

@henriquepaulalima
Copy link

henriquepaulalima commented Dec 2, 2023

I tried the solutions above but none of it worked, I just manage to solve downgrading my packges and changing some setting on the next.config.js

My package.json looked like this:

{
"scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "next start",
    "lint": "next lint",
    "email": "email dev --dir src/emails"
  },
  "dependencies": {
    "@react-email/components": "0.0.6",
    "next": "^13.4.4",
    "react": "^18.2.0",
    "react-dom": "^18.2.0",
    "react-email": "1.9.3",
    "react-is": "^18.2.0",
  },
  "devDependencies": {
    "@types/node": "^17.0.45",
    "@types/react": "^18.2.7",
    "@types/react-dom": "^18.2.4",
    "typescript": "^5"
  }
}

And my next.config.js like this:

/** @type {import('next').NextConfig} */
const nextConfig = {
  reactStrictMode: true,
  experimental: {
    appDir: true,
  },
}

export default nextConfig

After changing these files, deleting node_modules, .react-email, package-lock.json and running npm i and npm run email the project worked just fine

@onlyhasbi
Copy link

What i do to resolve the issue

  1. delete folder generated by react-email .react-email | .react-email-temp
  2. delete node_modules
  3. run npm i
  4. install @react-email/render
  5. then npm email

@AlecRust
Copy link

AlecRust commented Dec 3, 2023

Installing @react-email/render in addition to the packages I already had installed, fixed it for me too.

@BlakePro
Copy link

BlakePro commented Dec 9, 2023

I resolve the issue using in next.config.js

  experimental: {
    serverComponentsExternalPackages: ['@react-email/render', '@react-email/tailwind']
  }

@monzim
Copy link

monzim commented Dec 9, 2023

It worked for me

    "@react-email/components": "^0.0.11",
    "@react-email/render": "0.0.9-canary.2",

my nextjs version: "next": "14.0.4",

@dewodt
Copy link

dewodt commented Dec 11, 2023

It worked for me

    "@react-email/components": "^0.0.11",
    "@react-email/render": "0.0.9-canary.2",

my nextjs version: "next": "14.0.4",

It works for me, Thank you.

@coleerikson
Copy link

Confirmed on "@react-email/render": "0.0.10" as well

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Type: Bug Confirmed bug
Projects
None yet
Development

Successfully merging a pull request may close this issue.