快速回答: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 孤島:不 hydrate,直接原生 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>Email <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 預算——這正是孤島架構的原意:hydrate 花在互動發生的地方,而不是鋪滿整份文件。

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 全流程實作 →