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

React项目用SendGrid发邮件遇fs/path模块解析失败求助

React浏览器端使用SendGrid发送邮件的问题及解决方案

我在浏览器端React应用中尝试使用SendGrid模块通过联系表单发送邮件,但运行npm start时编译失败,提示无法在@sendgrid/helpers/classes中解析fs与path模块,同时收到webpack 5不再默认提供Node.js核心模块polyfill的警告。

相关代码

card.tsx(联系表单React组件)

import React, { useState, useEffect, useRef } from "react";
import cardcss from "./Card.module.css";

interface CardProps {
  imageUrl: string;
  title: string;
  body: string;
  onSubmitForm: (contactData: {
    Name: string;
    Email: string;
    Message: string;
  }) => void;
}

function Card(props: CardProps) {
  const [message, setMessage] = useState("");
  const messageInputRef = useRef<HTMLInputElement>(null);

  useEffect(() => {
    if (messageInputRef.current) {
      messageInputRef.current.focus();
      messageInputRef.current.setSelectionRange(0, 0);

      // Delay setting the cursor position
      setTimeout(() => {
        messageInputRef.current?.setSelectionRange(0, 0);
      }, 0);
    }
  }, []);

  const handleFocus = (event: React.FocusEvent<HTMLInputElement>) => {
    event.target.setSelectionRange(0, 0);
  };

  function ContactForm() {
    const nameInputRef = useRef<HTMLInputElement>(null);
    const emailInputRef = useRef<HTMLInputElement>(null);
    const messageInputRef = useRef<HTMLInputElement>(null);
    const submitRef = useRef<HTMLButtonElement>(null);

    function submitHandler(event: React.FormEvent<HTMLFormElement>) {
      event.preventDefault();

      if (
        nameInputRef.current &&
        emailInputRef.current &&
        messageInputRef.current
      ) {
        const nameInput = nameInputRef.current.value;
        const emailInput = emailInputRef.current.value;
        const messageInput = messageInputRef.current.value;

        const contactData = {
          Name: nameInput,
          Email: emailInput,
          Message: messageInput,
        };

        if (submitRef.current) {
          const submittedButton = submitRef.current.value;

          const contactDataWithButton = {
            ...contactData,
            submitbutton: submittedButton,
          };

          // Do something with contactDataWithButton
          props.onSubmitForm(contactData);
        }
      }
    }


    return (
      <div className={cardcss.cardcontainer}>
        <div className={cardcss.imagecontainer}>
          <img src={props.imageUrl} alt="" />
        </div>

        <div className={cardcss.cardtitle}>
          <h3>{props.title}</h3>
        </div>

        <div className={cardcss.cardbody}>
          <p>{props.body}</p>

          <form onSubmit={submitHandler}>
            <div>
              <label className={cardcss.namelabel} htmlFor="name">
                Name:
              </label>
              <input
                className={cardcss.nameinput}
                type="text"
                id="name"
                name="name"
                ref={nameInputRef}
              />
            </div>

            <div className={cardcss.emailcontainer}>
              <label className={cardcss.emaillabel} htmlFor="email">
                Email:
              </label>
              <input
                className={cardcss.emailinput}
                type="text"
                id="email"
                name="email"
                ref={emailInputRef}
              />
            </div>

            <div className={cardcss.messagecontainer}>
              <label className={cardcss.messagelabel} htmlFor="message">
                Message:
              </label>
              <input
                ref={messageInputRef}
                className={cardcss.messageinput}
                type="text"
                id="message"
                name="message"
                value={message}
                onChange={(e) => setMessage(e.target.value)}
                onFocus={handleFocus}
              />

              <button className={cardcss.sendbutton} ref={submitRef}>
                Send
              </button>
            </div>
          </form>
        </div>
      </div>
    );
  }

  return <ContactForm />;
}

export default Card;

contact.tsx(使用Card组件的React组件)

import React, { useRef } from "react";
import contactcss from "./Contact.module.css";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import Card from "../../components/ui/Card";
import {
  faInstagram,
  faFacebook,
  faLinkedin,
  faDiscord,
} from "@fortawesome/free-brands-svg-icons";
import emaillogo from "../../resources/email-logo.png";
import { setApiKey, send } from "@sendgrid/mail";

const Contact: React.FC = () => {
  const inputRef = useRef<HTMLInputElement>(null);

  const handleClick = () => {
    if (inputRef.current) {
      inputRef.current.focus();
      inputRef.current.setSelectionRange(0, 0);
    }
  };

  setApiKey(
    "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"
  );

  function submitFormHandler(contactData: {
    Name: string;
    Email: string;
    Message: string;
  }) {
    const { Name, Email, Message } = contactData;

    const msg = {
      to: "paristiffany12@gmail.com",
      from: Email,
      subject: "GDSC Contact Form - New Message",
      text: `Message from: ${Name}\nMessage: ${Message}`,
      html: `<p>Message from: ${Name}</p><p>Message: ${Message}</p>`,
    };

    send(msg)
      .then(() => {
        console.log("Email sent successfully");
      })
      .catch((error) => {
        console.error(error.toString());
      });
  }

  return (
    <div>
      <div>
        <div className={contactcss.background}>
          <img
            className={contactcss.emaillogo}
            src={emaillogo}
            alt="Contact Us"
          />
          <div className={contactcss.contact}>Contact Us</div>
        </div>
      </div>

      <Card
        title="We're keen to hear from you!"
        imageUrl="https://images.app.goo.gl/D6m6hHMnP1gjsKKV7"
        body=""
        onSubmitForm={submitFormHandler}
      />

      <div className={contactcss.whitebackground}>
        <div className={contactcss.socials}>
          <h1>Follow Our Socials</h1>
          <p className={contactcss.socialmedia}>
            Stay connected with GDSC USYD by following us on our social media
            channels:
          </p>
          <div>
            <a href="https://www.instagram.com/gdscusyd/">
              <FontAwesomeIcon
                className={contactcss.instagram}
                icon={faInstagram}
              />
            </a>

            <a href="https://www.facebook.com/gdsc.usyd">
              <FontAwesomeIcon
                className={contactcss.facebook}
                icon={faFacebook}
              />
            </a>

            <a href="https://discord.com/channels/872033047652990986/872033047652990989">
              <FontAwesomeIcon
                className={contactcss.discord}
                icon={faDiscord}
              />
            </a>

            <a href="https://www.linkedin.com/company/gdsc-usyd/">
              <FontAwesomeIcon
                className={contactcss.linkedin}
                icon={faLinkedin}
              />
            </a>
          </div>
        </div>
      </div>
    </div>
  );
};

export default Contact;

indexfirebase.js(处理邮件发送的Node.js文件)

"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
var express_1 = require("express");
var mail_1 = require("@sendgrid/mail");
var path_browserify_1 = require("path-browserify");
// Set up SendGrid API key
mail_1.default.setApiKey("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");
// Create Express app
var app = (0, express_1.default)();
// Middleware to parse JSON requests
app.use(express_1.default.json());
// Route to handle sending emails
app.post("/send-email", function (req, res) {
    var _a = req.body, Name = _a.Name, Email = _a.Email, Message = _a.Message, submitbutton = _a.submitbutton;
    var msg = {
        to: "paristiffany12@gmail.com",
        from: "gdscusyd@gmail.com",
        subject: "Example Subject",
        text: "Name: ".concat(Name, "\nEmail: ").concat(Email, "\nMessage: ").concat(Message, "\nSubmit Button: ").concat(submitbutton),
        html: "<p>Name: ".concat(Name, "</p><p>Email: ").concat(Email, "</p><p>Message: ").concat(Message, "</p><p>Submit Button: ").concat(submitbutton, "</p>"),
    };
    mail_1.default
        .send(msg)
        .then(function () {
            res.status(200).json({ message: "Email sent successfully" });
        })
        .catch(function (error) {
            console.error(error.toString());
            res.status(500).json({ error: "Failed to send email" });
        });
});
// Set up fallback for path module
path_browserify_1.resolve.fallback = { path: path_browserify_1.require.resolve("path-browserify") };
// Start the server
app.listen(3000, function () {
    console.log("Server started on port 3000");
});

疑问

  1. 能否在浏览器端React应用中使用SendGrid模块?
  2. 是否可以修复该错误,使SendGrid在React应用中正常运行?
  3. 若不可行,React应用中联系表单发送邮件有哪些替代方案?

解答

1. 能否在浏览器端React应用中使用SendGrid模块?

不行。SendGrid官方SDK依赖Node.js核心模块(如fs、path),这些模块仅能在Node.js环境运行,浏览器不支持。另外,直接在浏览器端配置SendGrid API密钥会导致密钥泄露,任何用户都能通过开发者工具获取你的密钥,风险极高。

2. 是否可以修复该错误,使SendGrid在React应用中正常运行?

强行通过polyfill(如path-browserify、fs-browserify)补全Node.js模块能解决编译问题,但绝对不推荐:

  • 即便编译通过,浏览器环境下SendGrid SDK的部分功能仍无法正常工作;
  • API密钥暴露的安全问题无法解决,恶意用户可能用你的密钥发送垃圾邮件,导致你的SendGrid账号被封禁。

3. 替代方案

方案一:用现有Node.js后端转发请求

你已经写好了indexfirebase.js这个Express后端,只需调整前端代码,让表单数据发送到后端接口,再由后端调用SendGrid发邮件:

  • 前端修改:删除contact.tsx里的SendGrid相关代码,替换为fetch请求后端的/send-email接口:
// contact.tsx中的submitFormHandler修改为
function submitFormHandler(contactData: {
  Name: string;
  Email: string;
  Message: string;
}) {
  fetch('http://localhost:3000/send-email', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({...contactData, submitbutton: 'Send'}),
  })
  .then(res => res.json())
  .then(data => console.log(data.message))
  .catch(err => console.error(err));
}
  • 注意:部署时需处理跨域问题,可在Express后端添加cors中间件;另外,后端的API密钥要存到环境变量中,禁止硬编码。

方案二:使用Serverless函数

如果不想维护独立后端,可使用Vercel Functions、Netlify Functions这类Serverless服务,编写简单的函数处理邮件发送,前端直接调用该Serverless函数的接口即可。

方案三:使用第三方表单服务

比如Formspree、FormSubmit这类服务,只需将表单的action指向它们提供的接口,无需编写任何后端代码,配置简单,适合小型项目。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.18 06:12:02