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

Remix项目集成Resend与React Email遇stream动态导入不支持错误

Remix + Resend + React Email 密码重置问题解决

错误原因分析

“Dynamic require of ‘stream’ is not supported” 是因为React Email的渲染逻辑(如render/renderAsync)被意外在浏览器端执行,而stream是Node.js核心模块,浏览器环境不支持。Remix作为全栈框架,必须确保邮件渲染和发送逻辑仅在服务器端路由的loader/action中执行,不能在客户端组件里调用。

核心解决方案步骤

1. 确保邮件发送逻辑仅在服务器端运行

Remix的action/loader是服务器端专属执行环境,把邮件发送和模板渲染放在action里,不要在客户端表单的onSubmit等逻辑中调用。

密码重置页面示例

// app/routes/reset-password.tsx
import { Form, useActionData } from "@remix-run/react";

export default function ResetPasswordPage() {
  const actionData = useActionData<typeof action>();

  return (
    <div>
      <h1>重置密码</h1>
      <Form method="post">
        <input type="email" name="email" placeholder="输入邮箱" required />
        <button type="submit">发送重置链接</button>
        {actionData?.error && <p style={{ color: "red" }}>{actionData.error}</p>}
        {actionData?.success && <p style={{ color: "green" }}>{actionData.success}</p>}
      </Form>
    </div>
  );
}

// 服务器端action,仅在Node环境运行
export async function action({ request }: { request: Request }) {
  const formData = await request.formData();
  const email = formData.get("email") as string;

  try {
    // 生成重置令牌(替换为你的令牌生成逻辑)
    const resetToken = crypto.randomBytes(32).toString("hex");
    // 保存令牌到数据库(省略具体逻辑)

    // 调用邮件发送函数
    await sendPasswordResetEmail(email, resetToken);
    return { success: "重置链接已发送到你的邮箱" };
  } catch (error) {
    console.error(error);
    return { error: "发送邮件失败,请稍后重试" };
  }
}

2. 修复邮件发送辅助函数

确保用React Email的服务器端渲染方法,且不在客户端引入相关代码。

邮件发送工具函数示例

// app/utils/send-email.ts
import { Resend } from "resend";
import { renderAsync } from "@react-email/render";
import PasswordResetEmail from "../emails/PasswordResetEmail";

const resend = new Resend(process.env.RESEND_API_KEY);

export async function sendPasswordResetEmail(email: string, resetToken: string) {
  // 仅在服务器端渲染邮件模板
  const html = await renderAsync(
    <PasswordResetEmail resetToken={resetToken} />
  );

  await resend.emails.send({
    from: "你的应用 <noreply@yourdomain.com>",
    to: email,
    subject: "重置你的密码",
    html,
  });
}

3. 调整React Email模板

模板只做纯React组件渲染,不要包含服务器端专属代码,避免触发动态依赖加载。

密码重置邮件模板示例

// app/emails/PasswordResetEmail.tsx
import { Html, Body, Container, Button, Text } from "@react-email/components";

type Props = { resetToken: string };

export default function PasswordResetEmail({ resetToken }: Props) {
  const resetUrl = `https://yourdomain.com/reset-password/${resetToken}`;

  return (
    <Html>
      <Body style={{ fontFamily: "Arial, sans-serif" }}>
        <Container style={{ maxWidth: "600px", margin: "0 auto", padding: "20px" }}>
          <Text>你请求重置密码,请点击下方链接完成操作:</Text>
          <Button
            href={resetUrl}
            style={{
              backgroundColor: "#0070f3",
              color: "white",
              padding: "10px 20px",
              textDecoration: "none",
              borderRadius: "4px",
              display: "inline-block",
              marginTop: "16px",
            }}
          >
            重置密码
          </Button>
          <Text style={{ marginTop: "16px" }}>如果不是你操作,请忽略此邮件。</Text>
        </Container>
      </Body>
    </Html>
  );
}

4. Remix打包配置优化(若问题仍存在)

如果错误持续,在remix.config.js中添加Node.js模块的polyfill,或排除客户端不需要的依赖:

// remix.config.js
/** @type {import('@remix-run/dev').AppConfig} */
export default {
  ignoredRouteFiles: ["**/.*"],
  serverModuleFormat: "cjs", // 确保服务器端用CommonJS,避免ES模块兼容问题
  browserNodeBuiltinsPolyfill: {
    modules: {
      stream: true, // 仅必要时为浏览器端polyfill stream模块
    },
  },
};

关键注意事项

  • 禁止在客户端组件中调用renderAsync或Resend的send方法,这些都是服务器端专属逻辑。
  • 确保RESEND_API_KEY仅存在于服务器端环境变量,不要暴露给客户端。
  • 测试时使用Remix开发服务器,确认action在Node环境执行,而非浏览器。

内容的提问来源于stack exchange,提问作者Miles Donald

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.22 01:38:10