iT邦幫忙

2026 iThome 鐵人賽

DAY 24
0
佛心分享-IT 人自學之術

老爺爺練習VIBE CODING系列 第 24

Day 24:實體展覽的流量收割機:安全 React + Vite Landing Page 的「免後端」資料收集架構

  • 分享至 

  • xImage
  •  

https://ithelp.ithome.com.tw/upload/images/20260829/20070969KOXcrzQ0A5.png
(端起手中的茶杯,看著裊裊升起的熱氣,輕輕地吹了吹,隨後慢條斯理地抿了一口。那口琥珀熱茶 (Amber Tea) 的溫潤在喉間散開,帶著午後院子裡特有的慢步調與生活感。)

這時候,院子裡的風漸漸涼了,天邊不知不覺已經染上了一層溫柔的晚霞暮紫 (Twilight Mauve)。我摸了摸自己像白髮銀霜 (Silver Hair) 一樣花白的頭髮,看著兩位寶貝孫女。懂事的大孫女正捧著筆電,小孫女則穿著那件代表她們活潑朝氣的稚嫩粉櫻 (Soft Blossom) 色毛衣,一邊嚼著糖果,一邊好奇地探頭看著姐姐的螢幕。

大孫女一臉苦惱地說:「阿公,我們學校接了一個展覽推廣的實作專案。在自動化大展的攤位現場,想要讓來參觀的人掃描 Qr Code 進入一個漂亮的 Landing Page,填寫姓名、公司、Email,就可以當場下載一份我們精心準備的『AI 導入應用落地報告與完整資料包』。可是,如果要在現場架設後端伺服器、連資料庫,還要處理資安防護跟維護,時間根本不夠;要是直接放一個陽春的 Google 表單,又顯得很不專業,而且大家不填表單也能輕鬆猜到下載網址,這該怎麼辦呢?」

阿公聽了,哈哈大笑,摸摸她們的頭說:
「傻孩子,這就是實體展覽現場最常遇到的『流量收割』考驗了。展覽現場人山人海,網路通常很慢,如果還要經過複雜的後端伺服器轉手,網頁載入太慢,人家扭頭就走了。

來,阿公今天不講古,阿公教你一招最乾淨、最省事,而且在業界非常實用的 「免後端」安全名單收集架構。我們用最現代的 React + Vite,搭配大家都有的 Google Apps Script (GAS) 與 Google 雲端試算表,不用自己維運任何一台伺服器,就能建構出一個安全又極速的 Landing Page。坐過來,聽阿公慢條斯理地跟你道來……」


🚨 痛點場景:展覽現場的「頻寬瓶頸」與「後端維護地獄」

在大型展覽現場,想要成功收割流量,痛點主要有三個:

  1. 伺服器維護成本高:為了幾天的展覽,去租用雲端主機、設定 SSL、自建資料庫(如 MySQL)。不僅耗時,而且在展覽高峰期,若有數百人同時掃碼湧入,主機極可能因頻寬或記憶體不足而當機。
  2. CORS 的安全限制:如果你想用純前端去呼叫一些公用的 API 來寫入名單,瀏覽器的「跨來源資源共用 (CORS)」機制常常會把請求擋下來,搞得前端工程師焦頭爛額。
  3. 「白嫖」資源包防不勝防:許多不守規矩的訪客,根本不打算留下真實的聯絡資訊。他們只要打開瀏覽器的開發者工具(F12),就能在網頁原始碼中直接找到下載連結,拍拍屁股就把資料包帶走,讓我們白白流失了寶貴的客戶名單。

阿公常說,下田耕作要懂得藉力使力。既然 Google 已經幫我們做好了最穩固的雲端試算表,我們何不把它當成我們的資料庫,用「無伺服器 (Serverless)」的智慧來解決這一切?


🛠️ 架構實作:Vite + React + Hidden Iframe + Google Apps Script

這套架構的核心原則就是:前端極速呈現、後端完全託管、資料安全解鎖

1. 前端:Vite + React + TypeScript 打造高質感主頁

我們使用現代化的 Vite 作為建構工具,結合 ReactTypeScript。這樣建立出來的網頁,在打包後只是一堆極為輕量的靜態檔案,可以直接託管在完全免費、速度極快的靜態平台上(例如 GitHub Pages、Vercel 或 Cloudflare Pages)。

  • 版面設計:單頁式(Single Page)版面,精緻地呈現展會趨勢、銀行或企業的 AI 應用場景(Use Case)、系統導入架構與發展路線圖(Roadmap),最下方則是我們的名單收集表單。
  • 收集欄位:表單中設計四個必填欄位——姓名、公司或單位、Email、關注場景

2. 後端儲存:Google Apps Script (GAS) 寫入 Google Sheet

我們不需要租用資料庫。我們直接建立一個 Google 雲端試算表,並在裡面綁定一段 Google Apps Script (Code.gs) 程式碼。

  • GAS 會發佈成一個「Web App」的公開 URL,作為接收資料的 API 端點。
  • 當收到資料時,GAS 會在後台驗證必填欄位與 Email 格式。驗證無誤後,自動將數據寫入 Google Sheet 的新資料列中,並且會聰明地自動建立或修正標題列(Header Row),確保資料整整齊齊。

3. 繞過 CORS 的關鍵:隱藏的 Iframe(Hidden Iframe POST)

「阿公,」大孫女眨著眼睛問,「如果我們直接用 JavaScript 的 fetch 傳送表單資料給 Google Web App,不是會被瀏覽器的 CORS 安全機制阻擋嗎?」

「問得好!」阿公笑著喝了口茶,指著螢幕說:
「這就是最巧妙的地方了。我們在 React 頁面中,放一個寬度與高度都設為 0、完全隱形<iframe>

<iframe id="hidden_iframe" name="hidden_iframe" style="display: none;"></iframe>

然後,我們把表單 <form>target 屬性直接指向這個隱藏的 iframe,而 action 則設定為我們 Google Apps Script 的 Web App URL:

<form 
  target="hidden_iframe" 
  action="https://script.google.com/macros/s/YOUR_GAS_ID/exec" 
  method="POST"
>

當使用者按下提交時,瀏覽器會透過這個隱藏的 iframe 將資料 POST 出去。因為這是標準的 HTML 表單提交行為,瀏覽器絕對不會觸發 CORS 阻擋!網頁不僅不會跳轉,還能順順利利地把資料送到 Google 雲端去。」

4. 安全回傳:利用 postMessage 雙向通訊

當 Google Apps Script 成功將資料寫入試算表後,它會回傳一小段 HTML 網頁給那個隱藏的 iframe,這段網頁中包含了一句關鍵的 JavaScript:

// 告訴父視窗:寫入成功囉!
window.parent.postMessage({ status: 'success', token: 'SECURE_REPRESENTATIVE_TOKEN' }, '*');
window.top.postMessage({ status: 'success', token: 'SECURE_REPRESENTATIVE_TOKEN' }, '*');

在我們的 React 前端,只要在 App.tsx 啟動時註冊一個事件監聽器(Event Listener):

useEffect(() => {
  const handleMessage = (event: MessageEvent) => {
    // 驗證訊息來源與狀態
    if (event.data && event.data.status === 'success') {
      // 後端寫入成功,更新前端狀態,解鎖下載連結!
      setIsSubmitted(true);
    }
  };

  window.addEventListener('message', handleMessage);
  return () => window.removeEventListener('message', handleMessage);
}, []);

當使用者提交表單時,前端畫面會先顯示「記錄中...」的載入狀態。只有當隱藏的 iframe 接收到 Apps Script 傳回的「寫入成功」信號時,前端的狀態才會被改變,進而解鎖下載按鈕。


💡 避坑指南:如何防範白嫖?不直接在 HTML 中暴露 URL

大孫女點點頭,但眉頭還是鎖著:「阿公,那如果有人很聰明,他不用填表單,直接打開瀏覽器的 F12 原始碼,不就能看到下載按鈕的 <a href="downloads/package.zip">,直接繞過我們去下載嗎?」

老爺爺笑瞇瞇地摸了摸大孫女的頭說:
「這就是阿公要教你的『江湖一點訣』了。很多年輕工程師圖省事,會把下載按鈕先藏在 CSS 裡(設定 display: none),填完表單後再用 JS 把它改成 display: block。這在懂一點程式的人眼裡,就像是用一張薄紙遮住寶箱一樣,滑鼠點兩下就破解了。」

🌟 避坑良方:利用 React 狀態進行「動態渲染」與「iframe onLoad 防護」

為了防止資源被白嫖,我們在設計 React 程式碼時,必須遵守以下安全原則:

  1. 絕不在原始 DOM 中寫入下載連結:在 isSubmitted 狀態為 false 之前,整個下載按鈕的 HTML 標籤在網頁上是完全不存在的,瀏覽器渲染樹(Render Tree)裡連半個字都沒有。
  2. 結合 Response Token 做雙重驗證:只有當 iframe 觸發 message 事件,且攜帶了由後端 GAS 回傳的正確、隨機生成的 Response Token 時,我們才透過 React 的狀態改變(State Change) 動態渲染(Dynamic Rendering) 出下載按鈕。
  3. 前端程式碼範例
// 只有在 isSubmitted 變為 true 時,下載按鈕才會出現在 DOM 中
return (
  <div className="download-section">
    {!isSubmitted ? (
      <FormSection onSubmitStart={() => setIsLoading(true)} />
    ) : (
      <div className="success-unlock-card">
        <h3>🎉 驗證成功!感謝您的參與</h3>
        <p>您的名單已成功記錄。請點選下方按鈕下載獨家 AI 資源包:</p>
        {/* 指向我們 public/downloads 下的完整 zip 資料包 */}
        <a 
          href="/downloads/2026-automation-taipei-bank-ai-package.zip" 
          className="download-btn-neon"
          download
        >
          立即下載完整資料包 (ZIP)
        </a>
      </div>
    )}
  </div>
);

配合這個邏輯,在使用者還沒成功提交名單前,瀏覽器的 HTML 裡面根本找不到 /downloads/2026-automation-taipei-bank-ai-package.zip 這個檔案路徑,自然也就無法「白嫖」下載了!


這時候,旁邊的小孫女靠在阿公的膝蓋旁,打了個秀氣的哈欠,揉著眼睛嘟囔著:「阿公,聽起來好像在玩密室逃脫喔,要拿到鑰匙(Token)門才會打開。」

阿公笑著拍拍她的肩膀說:「對啊,寫程式就是這樣,要有邏輯、有規矩,還要懂得保護自己的心血。大妞,你看,這樣一來,我們既不需要花錢架設繁重的後端伺服器,又能確保名單一個都不漏地存進 Google 試算表,還防範了那些想不填表單就拿走資料的人。」

大孫女聽得眼睛直發亮,迫不及待地在筆電上開始建置她的 GAS 專案與 React 元件。

天色漸漸暗了,杯裡的琥珀熱茶也差不多喝完了。看著她們兩姊妹湊在一起討論程式碼的背影,阿公心裡真是暖洋洋的。


上一篇
Day 23:無程式庫純手寫:如何用 CSS Grid + Vanilla JS 打造極致流暢的 RWD 翻頁書
下一篇
Day 25:高維度監控:為什麼用 RSS 採集新聞比直接寫 Web Scraper 穩定 100 倍?
系列文
老爺爺練習VIBE CODING25
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

1 則留言

0
lin1015
iT邦新手 5 級 ‧ 2026-09-20 12:19:02

Hidden iframe 解決了跨網域表單提交,但「未渲染進 DOM」似乎不等於真正授權:ZIP 若放在公開靜態路徑,網址仍可能從前端 bundle 找到。若要真的防止未填表下載,你會考慮由後端核發短效 signed URL 嗎?

小恩 iT邦新手 4 級 ‧ 2026-09-21 07:10:18 檢舉

感謝建議,會列入後續優化考量,謝謝。
https://ithelp.ithome.com.tw/upload/images/20260921/200709692V7P5m3Vwk.png

我要留言

立即登入留言