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"); });
疑问
- 能否在浏览器端React应用中使用SendGrid模块?
- 是否可以修复该错误,使SendGrid在React应用中正常运行?
- 若不可行,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
相关产品推荐
相关产品推荐

