You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用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异步组件的方式处理。

修复步骤

  1. 将模板组件改为异步组件,用async/await等待QR Code的Data URL解析完成
  2. 调整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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.15 14:47:35