指南
Next.js 联系表单(无需后端)
Next.js 不需要自己的表单接收器。您可以把同样的原生 HTML form action 放进任何 Server Component,或者 - 如果您想要客户端的成功和错误状态 - 用一个带 fetch 的小型 Client Component。两者都直接 POST 到 Formhook。
使用原生 HTML 表单
您不需要 API 路由。表单直接 POST 到 Formhook,您的框架完全不必接触提交内容。
<form action="https://formhook.app/f/YOUR_API_KEY" method="POST">
<label>
Email
<input type="email" name="email" required />
</label>
<label>
Message
<textarea name="message" required></textarea>
</label>
<!-- honeypot: bots fill this; humans don't see it -->
<input type="text" name="_gotcha" tabindex="-1" autocomplete="off" hidden />
<!-- optional: send users to a thank-you page after a native POST -->
<input type="hidden" name="_redirect" value="https://example.com/thanks" />
<button type="submit">Send</button>
</form>或在 Next.js 中通过 fetch 提交
如果您希望在不刷新整个页面的情况下获得客户端的成功/错误状态,使用这个版本。
"use client";
import { useState } from "react";
export function ContactForm() {
const [status, setStatus] = useState<"idle" | "sending" | "ok" | "error">("idle");
async function onSubmit(e: React.FormEvent<HTMLFormElement>) {
e.preventDefault();
setStatus("sending");
const form = e.currentTarget;
const data = Object.fromEntries(new FormData(form));
const res = await fetch("https://formhook.app/f/YOUR_API_KEY", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(data),
});
setStatus(res.ok ? "ok" : "error");
if (res.ok) form.reset();
}
if (status === "ok") return <p>Thanks - we'll be in touch.</p>;
return (
<form onSubmit={onSubmit}>
<input type="email" name="email" placeholder="Email" required />
<textarea name="message" placeholder="Message" required />
<input type="text" name="_gotcha" tabIndex={-1} hidden />
<button type="submit" disabled={status === "sending"}>
{status === "sending" ? "Sending…" : "Send"}
</button>
{status === "error" && <p>Something went wrong. Please try again.</p>}
</form>
);
}工作原理
原生 form action 可在任何 Next.js 页面或 layout 中工作,包括 Server Components。无需 app/api/contact/route.ts、Server Action 或 middleware。如果您想要带成功和错误状态的客户端 UX,第二个代码片段展示了规范模式:"use client"、useState、onSubmit、fetch 到 formhook.app/f/YOUR_API_KEY。honeypot (_gotcha) 和可选的 _redirect 与 HTML 指南中的特殊字段相同。
普通表单还是 fetch:该用哪个?
想要零 JavaScript 时用普通的 action 表单:它能在 Server Component 中工作,即使 hydration 失败也照常运行。代价是提交时浏览器会离开您的页面,所以请加一个隐藏的 _redirect 输入,值为您某个允许来源下的绝对 URL,例如 https://example.com/thanks。没有它,访问者会看到一段原始 JSON 响应。
想让成功或错误提示就地显示、发送时按钮置灰,就用 fetch 版本。普通表单提交可以重定向;JSON 的 fetch 提交会得到 JSON 响应,因此 _redirect 在那里会被忽略。更详细的说明见AJAX 表单提交。
修复本地开发中的 403 origin_not_allowed
Formhook 只接受来自您在表单允许的 origin中列出的来源的浏览器提交,并且是精确匹配:协议、主机和端口都要一致。http://localhost:3000、https://example.com 和 https://www.example.com 是三个不同的 origin。请添加您的开发 URL、生产域名以及测试用的预览部署域名,不要带路径或末尾斜杠。列表为空时会拒绝所有浏览器请求。
如果提交仍然失败,响应正文会说明原因:origin_not_allowed(403)表示列表中缺少该 origin,form_not_found(404)表示 API 密钥有误,rate_limited(429)表示需要等待 Retry-After 指定的秒数。更多内容见联系表单不工作?
API 密钥放在哪里
无论怎么放,密钥最终都会出现在您的 HTML 或 JavaScript 包里,所以请把它当作公开标识符,而不是机密。用环境变量 NEXT_PUBLIC_FORMHOOK_KEY 仍然方便,可以在开发和生产中使用不同的表单。
真正保护表单的是 Formhook 一侧:允许的 origin 列表、_gotcha 蜜罐、按 IP 和按表单的速率限制,以及可选的 Cloudflare Turnstile。origin 列表能阻止其他网站从浏览器发起提交,但它不是身份验证,因为脚本可以发送任意 Origin 头。对付机器人请依靠这些防垃圾层;联系表单的垃圾信息防护对它们做了比较。
改用 Server Action 提交
如果您想在服务端补充数据或在发送前校验,可以从 Server Action 或 route handler 调用 Formhook。服务器请求不带 Origin 头,因此必须带上 X-Auth-Token 头,该令牌在表单设置中生成。请把令牌放在仅服务端可见的环境变量里,绝不要放进 Client Component。大多数联系表单用不到这一点,所以上面的代码片段没有包含它。完整的请求格式见文档。
上线之前
- 把生产环境的 origin(带
https://)添加到允许的 origin。 - 保留
_gotcha蜜罐字段,并决定是否开启 Turnstile。 - 发送一条测试提交,确认它出现在仪表板并触发推送通知。
- 在表单下方加一段简短的隐私说明。符合 GDPR 的联系表单指南里有可改写的措辞。
之后会发生什么
- 提交一秒内出现在您的 Formhook 仪表盘中。
- 推送通知会在每个启用它的设备上触发。
- 从仪表盘直接回复(Pro 及以上)- 在每个项目都有自己联系路由的客户工作中很方便。
今天就上线您的 Next.js 联系表单。
免费注册、创建表单、粘贴 API 密钥。五分钟搞定。
免费开始免费层:5 个表单 · 每月 250 次提交 · 无需信用卡。
Next.js 联系表单 - 常见问题
我需要添加 API 路由或 Server Action 吗?
我能在 Server Component 中使用它吗?
如何在不编写速率限制 middleware 的情况下处理垃圾?
其他框架的指南
Migrating from Formspree? See how Formhook compares.