thedevstockgirl
thedevstockgirl4mo ago

Running into some cryptic error. Help needed.

I am running into some error. This is on React 19, Nextjs 15 and all latest Convex. Has anyone seen this?
No description
22 Replies
erquhart
erquhart4mo ago
Any chance you're using Cloudflare? This React bug mentions Astro specifically but looks very similar: https://github.com/facebook/react/issues/31827 Hmm might not even be cloudflare specific, I think the error message just came from a cloudflare worker that was doing ssr maybe This has popped up in a few other places with different tooling. MessageChannel is a Node API used for SSR, so this error is happening in a non-node context. Another maybe related issue here that does involve Next 15: https://github.com/resend/react-email/issues/1630 This comment from a Vercel team member seems to hit the root of the issue: https://github.com/vercel/next.js/issues/71865#issuecomment-2444377726
thedevstockgirl
thedevstockgirlOP4mo ago
Thank you. Looking into all the links you shared now.
lee
lee4mo ago
interesting that you're getting the same error from next (presumably in a vercel environment) and from the convex environment. neither of them implement MessageChannel. any idea why you might be importing "react-dom/server"?
thedevstockgirl
thedevstockgirlOP4mo ago
Ah. Yes. That's interesting. @erquhart , you saved the day. After much debugging, uninstalling and re-installing, react-email was indeed the culprit. Things worked once I commented out, and used pure html.
No description
thedevstockgirl
thedevstockgirlOP4mo ago
I initially tried jsx-email, but that had a bun of node things that made convex unhappy.
lee
lee4mo ago
hmm i think we want "react-email" to work. we'll look into it. can you share the exact version of react-email and react-dom and other packages that may be relevant (npm ls) 🙏
thedevstockgirl
thedevstockgirlOP4mo ago
Ah. It was not just react-email. Even after I removed react email, attempting to pass anything to the react prop of resend, triggered the same error. Just spent the last hour converting all to pure html with the help of claude. Then using the html prop instead with getSomeTemplate, which returns a html string. No errors. But If I pass the same html to the react prop, it shows the same error I just updated all our packages. So the latest of all relevant package. The only react in the api folder is from resend and react-email. So its def something with react. Once I got rid of that, all works now
thedevstockgirl
thedevstockgirlOP4mo ago
For example, the below does not work
No description
erquhart
erquhart4mo ago
Sounds like react 19 specifically
thedevstockgirl
thedevstockgirlOP4mo ago
Yes
thedevstockgirl
thedevstockgirlOP4mo ago
This works:
No description
thedevstockgirl
thedevstockgirlOP4mo ago
@erquhart Yes. React 19.
Eva
Eva4mo ago
Hi! I just ran into this exact issue tonight while trying to install and use react-email. Was there a solution identified?
"react": "^19.0.0",
"react-dom": "^19.0.0",
"@react-email/components": "0.0.32",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"@react-email/components": "0.0.32",
erquhart
erquhart4mo ago
Yeah, not using react. There’s a non-working and working example above
Eva
Eva4mo ago
RIP I love the simplicity of Convex Auth but might need to switch to Clerk
David Alonso
David Alonso3mo ago
Just want to flag that we're having the same issue here, going for pure html for now, but would love to get updates if React 19 works at some point
ballingt
ballingt3mo ago
Thanks for flagging, how would you describe the issue @David Alonso, "React emails don't work with React 19 in Convex?" Sounds like it's to do with some Node.js specific APIs? Sounds like we need to prioritize this, but I don't know what the issue is yet. If it's a general runtime API we can add it, but if the issue is that React Email now only supports Node.js for React 19 that's something we need to open an issue about
ballingt
ballingt3mo ago
GitHub
Error: A Node.js API is used (MessageChannel) which is not supporte...
Describe the Bug Using React 19 + Next.js 15 I want to use @react-email/render on edge runtime. However, I got this error: unhandledRejection: Error: A Node.js API is used (MessageChannel) which is...
David Alonso
David Alonso3mo ago
yeah that's how I'd describe it but only based on what solved it for me, which was downgrading to react 18.3 (which is what the auth template uses). I briefly tried the pure HTML approach but that didn't seem to work for me (i might have missed something)
erquhart
erquhart2mo ago
So two issues and solutions: 1. MessageChannel not supported: use a node action. 2. reactDOMServer.default.renderToPipeableStream is undefined: Add @react-email/components (or /render, whichever you import render from) to node.externalPackages in convex.json So the status is "React emails work with React 19 in Convex, but only for Node actions". It is possible to use the Convex runtime, but the @react-email/render browser module is attempting to run reactDOMServer.renderToReadableStream when the actual import shape is reactDOMServer.default.renderToReadableStream. Works when patched. Going to see if there's an elegant solution, otherwise might make a patch-package for this. Here's the patch steps for anyone that wants this working in Convex runtime (patch below): - Make sure you're using @react-email/render 1.0.5 (or @react-email/components 0.0.35, which exports render from the same version) - Add the file above to a patches/ directory alongside your package.json (repo or project root) - Set up patch package to apply: https://www.npmjs.com/package/patch-package /patches/@react-email+render+1.0.5.patch
diff --git a/node_modules/@react-email/render/dist/browser/index.mjs b/node_modules/@react-email/render/dist/browser/index.mjs
index 0d63ab6..74e9b0d 100644
--- a/node_modules/@react-email/render/dist/browser/index.mjs
+++ b/node_modules/@react-email/render/dist/browser/index.mjs
@@ -155,9 +155,9 @@ var render = (element, options) => __async(void 0, null, function* () {
const suspendedElement = /* @__PURE__ */ jsx(Suspense, { children: element });
const reactDOMServer = yield import("react-dom/server");
let html2;
- if (Object.hasOwn(reactDOMServer, "renderToReadableStream")) {
+ if (Object.hasOwn(reactDOMServer.default, "renderToReadableStream")) {
html2 = yield readStream(
- yield reactDOMServer.renderToReadableStream(suspendedElement)
+ yield reactDOMServer.default.renderToReadableStream(suspendedElement)
);
} else {
yield new Promise((resolve, reject) => {
diff --git a/node_modules/@react-email/render/dist/browser/index.mjs b/node_modules/@react-email/render/dist/browser/index.mjs
index 0d63ab6..74e9b0d 100644
--- a/node_modules/@react-email/render/dist/browser/index.mjs
+++ b/node_modules/@react-email/render/dist/browser/index.mjs
@@ -155,9 +155,9 @@ var render = (element, options) => __async(void 0, null, function* () {
const suspendedElement = /* @__PURE__ */ jsx(Suspense, { children: element });
const reactDOMServer = yield import("react-dom/server");
let html2;
- if (Object.hasOwn(reactDOMServer, "renderToReadableStream")) {
+ if (Object.hasOwn(reactDOMServer.default, "renderToReadableStream")) {
html2 = yield readStream(
- yield reactDOMServer.renderToReadableStream(suspendedElement)
+ yield reactDOMServer.default.renderToReadableStream(suspendedElement)
);
} else {
yield new Promise((resolve, reject) => {
David Alonso
David Alonso2mo ago
@Michael
erquhart
erquhart2mo ago
Forgot to add, in Convex runtime I think you still need to polyfill MessageChannel, just put the MessageChannel mock below in a file and import it first in whatever file your email rendering is happening in. You may be able to get away with a more minimal polyfill but this one works.
// polyfill MessageChannel without using node:events
if (typeof MessageChannel === "undefined") {
class MockMessagePort {
onmessage: ((ev: MessageEvent) => void) | undefined;
onmessageerror: ((ev: MessageEvent) => void) | undefined;

close() {}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
postMessage(_message: unknown, _transfer: Transferable[] = []) {}
start() {}
addEventListener() {}
removeEventListener() {}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
dispatchEvent(_event: Event): boolean {
return false;
}
}

class MockMessageChannel {
port1: MockMessagePort;
port2: MockMessagePort;

constructor() {
this.port1 = new MockMessagePort();
this.port2 = new MockMessagePort();
}
}

globalThis.MessageChannel =
MockMessageChannel as unknown as typeof MessageChannel;
}
// polyfill MessageChannel without using node:events
if (typeof MessageChannel === "undefined") {
class MockMessagePort {
onmessage: ((ev: MessageEvent) => void) | undefined;
onmessageerror: ((ev: MessageEvent) => void) | undefined;

close() {}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
postMessage(_message: unknown, _transfer: Transferable[] = []) {}
start() {}
addEventListener() {}
removeEventListener() {}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
dispatchEvent(_event: Event): boolean {
return false;
}
}

class MockMessageChannel {
port1: MockMessagePort;
port2: MockMessagePort;

constructor() {
this.port1 = new MockMessagePort();
this.port2 = new MockMessagePort();
}
}

globalThis.MessageChannel =
MockMessageChannel as unknown as typeof MessageChannel;
}

Did you find this page helpful?