快速回答:Next.js 客户端组件、Astro 孤岛、Hugo 纯静态页面,都能用一个托管接口存下每一条表单提交——不写 API 路由、不维护服务器。

前端又转回静态了。Next.js 在边缘渲染,Astro 默认不发一行 JavaScript,Hugo 把一堆 Markdown 编译成纯 HTML——快、可缓存、托管便宜。然后你会想起来,每个真实网站迟早都需要一样东西:表单。只要有访客需要给你提交点什么,那个问题就回来了:数据该存到哪?

本能答案是"加个 API 路由"。但对一个静态渲染的站点来说,这个答案会悄悄拆掉整个架构——你突然要负责一个 serverless 函数、它的冷启动、它的环境变量,还有背后的数据库。其实有个更轻的做法:页面保持原样的静态,表单把数据 POST 给一个托管接口,提交记录替你存好。

一个接口,三种提交形态

发布任意一个 FormLM 表单,你就得到一个 HTTP 接口。同样的数据,它按你的技术栈习惯的形态接收:

读写两端都开了 CORS,浏览器可以从任何来源直接提交——包括本地 file:// 打开的页面。不需要代理,不需要同源绕路。

Next.js:客户端组件跳过 API 路由

在 App Router 里,走一方 API 路由的表单要绕一圈服务端。当"后端"的全部职责只是"把这条对象存下来",这一圈就是纯开销。把组件标成客户端组件,直接 POST 到接口:

// app/feedback/page.tsx —— 没有 route.ts,没有环境变量,没有数据库
'use client';
import { useState } from 'react';

type Feedback = { name: string; rating: number; note?: string };
const ENDPOINT = 'https://formlm.me/api/v3/share/<your-token>';  // 发布后的 Data API 地址

export default function FeedbackForm() {
  const [done, setDone] = useState(false);

  async function submit(e: React.FormEvent<HTMLFormElement>) {
    e.preventDefault();
    const f = new FormData(e.currentTarget);
    const data: Feedback = {
      name: String(f.get('name')),
      rating: Number(f.get('rating')),
      note: f.get('note') ? String(f.get('note')) : undefined,
    };
    await fetch(ENDPOINT, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ data }),
    });
    setDone(true);
  }

  if (done) return <p>提交成功,感谢反馈。</p>;
  return (
    <form onSubmit={submit}>
      <input name="name" required />
      <input name="rating" type="number" min={1} max={5} required />
      <textarea name="note" />
      <button>提交</button>
    </form>
  );
}

Feedback 类型就是你与接口之间的全部契约。这个页面不依赖任何服务端运行时:照样预渲染,照样部署到任何静态托管,交互全部发生在浏览器里。如果你确实保留了服务端组件,同一个 POST 放进 server action 也一样成立——区别只是 fetch 这一行写在哪儿。

Astro 孤岛:不水合,直接原生 POST

Astro 默认只发 HTML,这意味着项目里最便宜的表单就是一个普通 HTML 表单。action 指向 /form 路径,每个字段名都会成为存下记录里的一个键:

// src/pages/contact.astro —— 客户端零 JavaScript
<form action="https://formlm.me/api/v3/share/<your-token>/form" method="POST"
      autocomplete="off">
  <label>称呼 <input name="name" required /></label>
  <label>邮箱 <input name="email" type="email" required /></label>
  <textarea name="message" required></textarea>
  <input type="hidden" name="page" value="/contact" />
  <!-- 蜜罐字段:真人永远不会填它 -->
  <input name="website" style="display:none" tabindex="-1" />
  <button type="submit">提交</button>
</form>

想要提交后不打断页面就给出成功提示?只把表单升级成一个孤岛(套一层很小的 React 或 Svelte 组件,标上 client:visible),换成上面的 fetch 写法。页面其余部分保持零 JS 预算——这正是孤岛架构的本意:水合花在有交互的地方,而不是铺满整个文档。

Hugo 与一切纯静态站点

Hugo、Eleventy、预渲染的 Gatsby、手写的纯 HTML——同一个原生表单 POST 就是通用答案,因为它不需要浏览器本来就会的事情以外的任何东西。页面生成一次,表单就永远可用,构建环节里没有后端什么事。把它放进 layouts/partials/form.html,整个集成就这一句话。

实操建议:每个表单都留一个蜜罐字段。机器人会填隐藏字段,真人从不会往看不见的地方打字。因为未知字段会自动长出自己的列,多一个 website 字段毫无成本——而存下的记录里这一列一旦有值,就是一眼可辨的垃圾提交,读数据时顺手过滤即可。

让 schema 跟着页面一起演进

静态站点是持续迭代的:下个月给联系表单加个 company 字段,或者给反馈组件加个量表。在 FormLM 接口上,"改后端"根本不是一个任务。提交一个接口没见过的新键,第一次提交时它就变成新的一列。想让这个特性用得安全,三个习惯:

不跳转的成功态

原生表单 POST 之后,浏览器会停在接口的响应页——对必须无 JS 运行的页面,这是可以接受的取舍。其余场景把访客留在页面上:await 这次 fetch,用一段感谢态换掉表单,在注意力最高的时刻给出下一步动作。对一个静态站点来说,这一小段反馈就是用户能看到的全部"后端体验"——值得花这两行代码。

"站点转回静态之后,后端问题并没有消失——只是变小了。而小正是重点:一个会存下每条提交的 URL,比一个函数、一条队列、一个数据库,划算太多了——对那些服务端需求只有一张表单的海量站点来说。"

🛠️ 把接口接进你的框架

表单长大的过程里,开发者专区替你保持事实是新的:

  • 每个发布后的表单一个 URL——JSON、原生 HTML POST,加 ?help 自述文档
  • 未知字段自动建列——payload 加键即加列,没有迁移这一步
  • 数据后台 + CSV 导出——给人看的,接口查询留给脚本
  • 读写全开 CORS——本地、磁盘文件、生产环境都直连
建一张表单,拿走接口 →

✅ 重点结论

  • 静态渲染的栈照样需要表单存储——接口方案让页面保持原来的静态
  • Next.js:客户端组件直接 POST JSON,API 路由整个跳过,页面照常预渲染
  • Astro 与 Hugo:原生表单 POST 指向 /form,零 JavaScript、零构建负担
  • 蜜罐字段几乎免费(未知键自动成列),让垃圾提交在读数据时一眼可见
  • schema 演进是特性:payload 里加个键,第一次提交接口就认识了它
  • 把访客留在页面上——await 之后用感谢态换掉表单

日常是服务客户、做测评而不是写功能?咨询顾问专区和培训效果评估专区教你一行代码不写,把同样的量表做出来。

常见问题

FormLM 能给我的 React 或 Next.js 应用当后端吗?

能。在客户端组件里把 JSON 以 {"data": {...}} 的形态直接 POST 到发布后的表单接口。CORS 已开启,不需要 API 路由和代理,页面照常预渲染、照常部署到任何静态托管。

纯静态、完全不用 JavaScript 的站点可以吗?

可以。把普通 HTML 表单的 action 指向接口的 /form 路径原生提交,字段名就是记录里的键。这正是 Hugo、Eleventy 以及零 JS 的 Astro 页面的标准做法。

怎么防垃圾提交?

最便宜的一道防线是隐藏蜜罐字段:机器人会填,真人永远不会。未知键会自动成列,所以蜜罐字段随记录一起存下——这一列有值的记录一眼就是垃圾,数据视图和查询结果里都好过滤。

加字段不想重新部署后端,怎么办?

在 payload 里直接把新键交上来就行。自动建列机制会在第一次提交时把陌生键变成新的一列;接口 URL 加 ?help 还能拿到当前字段的 Markdown 清单,你的代码——人或 Agent——都不会对着过期 schema 开发。

← 返回开发者专区 下一篇:MCP 全流程实操 →