使用React模板+Supabase+Nodemailer渲染HTML时出错求助
在Supabase中部署的Edge Function原本正常运行,通过cron Job从pgmq队列读取消息并发送邮件,但添加React Email模板美化邮件后出现异常。
Edge Function 简化代码
import React from 'npm:react@18.3.1' import nodemailer from 'npm:nodemailer@6.9.16' import 'jsr:@supabase/functions-js/edge-runtime.d.ts' import { render } from 'npm:@react-email/components@0.0.31' import { ReservationConfirmEmail } from './_templates/reservation-code.tsx' // ... type EmailPayload = { to: string subject: string content: string } // ... const transport = nodemailer.createTransport({ host: <SMTP_HOST>, port: <SMTP_PORT>, secure: false, auth: { user: <SMTP_USER>, pass: <SMTP_PASSWORD>, }, }) // ... // 实际发送邮件的方法 const sendEmail = async (params: EmailPayload): Promise<boolean> => { const { to, subject, content } = params try { // 使用React Email渲染邮件内容 const html = await render(React.createElement(ReservationConfirmEmail, { confirmationURL: content })) // 这行日志从未输出,执行在到达此处前崩溃 console.log(html) // ... 使用Nodemailer发送邮件 } catch (error) { console.error('Error sending email:', error) return false } } // ... Deno.serve(async (request: Request) => { // 此处为处理消息的代码 // sendEmail方法在这里被调用 })
React Email 模板代码 (_templates/reservation-code.tsx)
import * as React from 'npm:react@18.3.1' import QRCode from 'https://esm.sh/qrcode@1.5.1' import { Body, Container, Head, Heading, Html, Preview, Text, Img } from 'npm:@react-email/components@0.0.31' interface ReservationConfirmProps { confirmationURL: string } export const ReservationConfirmEmail = ({ confirmationURL }: ReservationConfirmProps) => { const qrCodeDataUrl = QRCode.toDataURL(confirmationURL).then() return ( <Html> <Head /> <Preview>Some cool preview</Preview> <Body style={styles.main}> <Container style={styles.container}> <Heading style={styles.h1}>Some text</Heading> <Text style={styles.text}>Some text</Text> <Container style={styles.qrContainer}> <Img src={qrCodeDataUrl} alt="Código QR" style={styles.qrImage} /> </Container> <Text style={styles.text}>Some text</Text> </Container> </Body> </Html> ) } export default ReservationConfirmEmail const styles = { main: { // ... }, container: { // ... }, h1: { // ... }, text: { // ... }, qrContainer: { // ... }, qrImage: { // ... }, }
报错信息
Error: Objects are not valid as a React child (found: object with keys {$$typeof, type, key, ref, props, _owner, _store}). If you meant to render a collection of children, use an array instead.
at retryNode (file:///var/tmp/sb-compile-edge-runtime/node_modules/localhost/react-dom/19.0.0/cjs/react-dom-server.browser.development.js:5867:17)
at performWork (file:///var/tmp/sb-compile-edge-runtime/node_modules/localhost/react-dom/19.0.0/cjs/react-dom-server.browser.development.js:6598:19)
at file:///var/tmp/sb-compile-edge-runtime/node_modules/localhost/react-dom/19.0.0/cjs/react-dom-server.browser.development.js:7239:16
at ext:core/01_core.js:302:9
核心问题
错误根源在模板里的QRCode.toDataURL(confirmationURL).then()这行代码:
QRCode.toDataURL()返回的是Promise对象,调用.then()后得到的依然是Promise(不是解析后的Data URL字符串)- React尝试渲染这个Promise对象时,无法识别其为合法的React子元素,因此抛出报错。
- 同时当前组件是同步组件,但生成QR Code是异步操作,必须用React异步组件的方式处理。
修复步骤
- 将模板组件改为异步组件,用
async/await等待QR Code的Data URL解析完成 - 调整Edge Function中的渲染逻辑,适配异步组件
修改后的模板代码
import * as React from 'npm:react@18.3.1' import QRCode from 'https://esm.sh/qrcode@1.5.1' import { Body, Container, Head, Heading, Html, Preview, Text, Img } from 'npm:@react-email/components@0.0.31' interface ReservationConfirmProps { confirmationURL: string } // 改为异步组件 export const ReservationConfirmEmail = async ({ confirmationURL }: ReservationConfirmProps) => { // 等待Promise解析,获取实际的Data URL字符串 const qrCodeDataUrl = await QRCode.toDataURL(confirmationURL) return ( <Html> <Head /> <Preview>Some cool preview</Preview> <Body style={styles.main}> <Container style={styles.container}> <Heading style={styles.h1}>Some text</Heading> <Text style={styles.text}>Some text</Text> <Container style={styles.qrContainer}> <Img src={qrCodeDataUrl} alt="Código QR" style={styles.qrImage} /> </Container> <Text style={styles.text}>Some text</Text> </Container> </Body> </Html> ) } export default ReservationConfirmEmail const styles = { main: { // ... }, container: { // ... }, h1: { // ... }, text: { // ... }, qrContainer: { // ... }, qrImage: { // ... }, }
修改Edge Function中的渲染逻辑
因为组件现在是异步的,需要先等待组件执行完成再渲染:
// 原代码 const html = await render(React.createElement(ReservationConfirmEmail, { confirmationURL: content })) // 修改为 const emailComponent = await ReservationConfirmEmail({ confirmationURL: content }) const html = await render(emailComponent)
额外说明
- React 18+支持异步组件,但在服务端渲染场景(React Email的render属于此类)中,必须正确处理异步逻辑
- 禁止在同步组件中直接传递Promise给React元素的属性,必须等待Promise解析完成后再传递实际值
内容的提问来源于stack exchange,提问作者rrcabrera9625

