框架選擇、靜態預產生、讓 Google 與 AI 搜尋都找得到、商業分析接法
這份回答三個問題:官網用什麼框架、怎麼讓搜尋引擎與 AI 搜尋(ChatGPT、Perplexity、Claude、Gemini)都看得到內容、怎麼接商業分析。結論是留 Lovable 拉回來的 TanStack Start,開 prerender 出純靜態檔;部署與 SEO/GEO 配備照公司已上線的
design-home-fe做法,檔案改寫成 TanStack 的對應寫法。
框架留 TanStack Start(Lovable 產出的那套),開 prerender 出純靜態 HTML。決策者 2026-10-21 最終裁示,不再討論。
曾評估轉 Next.js(與 design-home 對齊)並派過一棒試轉,決策者中途裁定沿用 TanStack:骨架已能跑、文案與版型都在,轉換只是換底不加價值;TanStack Start 本來就是 SSR 框架,prerender 一開就出每頁 index.html,SEO/GEO 要的「HTML 裡有內文」照樣成立。design-home 的 SEO 檔案拿來當內容參考(爬蟲清單、JSON-LD 欄位、llms.txt 骨架),寫法改成 TanStack 的 head()、public/ 靜態檔與路由。
| 問題 | 答案 |
|---|---|
| 純前端 Vue/React SPA 能不能做 SEO/GEO | 不能。Google 要第二輪才渲染 JS,GPTBot、ClaudeBot、PerplexityBot 不執行 JS,看到的是空殼 |
| 一定要 Next.js 嗎 | 不用。TanStack Start、Nuxt、Astro 都能出靜態 HTML;本站用 TanStack Start |
| 要不要改成 Astro | 不要,但它是最接近的第二名。技術上更適合官網(零 JS、Cloudflare 旗下、滿意度最高),輸在團隊要多懂一套、與 design-home 分家。何時反悔見一之二 |
| 留 Lovable 給的 TanStack Start 行不行 | 能做但不建議。框架太新(2026-10-13 才 1.0、nitro beta)、社群小出事難找答案、設定被 @lovable.dev/vite-tanstack-config 包住;留下等於替 Lovable 的選擇背一輩子,只為省一兩天 |
| 部署放哪 | Cloudflare Pages,純靜態。不用 Workers;需要時轉 Workers 只改部署指令、程式碼不動(見第三節之二) |
| 未來接 Payload CMS 行不行 | 行。Payload 是獨立後台,官網 build 時打它的 API 取內容,改完內容由 webhook 觸發重 build,design-home 就是這樣跑 |
| 未來要 call 自家 API 行不行 | 行。build 時抓一次的內容在 server component 裡 fetch;使用者操作時才打的(送表單)在 "use client" 元件裡打 |
決策者在意的是「不要順著單一提問改答案」,所以把需求攤開、三個候選者用同一套標準各打一次分。◎ 強、○ 可、△ 弱。
需求清單(來自決策者原話與 02 網站地圖):
| # | 需求 |
|---|---|
| R1 | SEO |
| R2 | AI SEO/GEO,AI 搜尋找得到 |
| R3 | 商業分析 |
| R4 | 搭配 Payload CMS,公司已有 design-home 那套 |
| R5 | 從 Lovable 轉一次,之後自己維護、不再用 Lovable |
| R6 | 架構定了就不要再變 |
| R7 | 部署 Cloudflare Pages |
| R8 | 決策者對前端生態陌生;團隊既有技能是 Vue(產品)與一個已上線的 Next 站 |
| R9 | 官網範圍小:18 固定頁+6 篇指南、中英、一個表單;不做登入、價格、試用 |
對照:
| 需求 | Next.js 靜態 export | Astro | TanStack Start(Lovable 給的) |
|---|---|---|---|
| R1 SEO 技術面 | ○ HTML 完整,但每頁帶 80~120KB React,Lighthouse 多在 80~90 | ◎ 預設零 JS,Lighthouse 98~100 | ○ 同 Next,帶 React runtime |
| R2 GEO | ○ 內文在 HTML;robots/sitemap/metadata 有內建 API | ◎ 內文在 HTML;2026 年多數評測點名對 AI 搜尋最友善 | ○ 內文在 HTML;配備要自己補 |
| R3 商業分析 | ○ 加一段 script | ○ 同 | ○ 同 |
| R4 Payload | ◎ Payload 本身跑在 Next 上;design-home 的 lib/payload.ts 直接抄 |
○ Astro 官方有 Payload 指南,走 REST;社群有 loader 與 live preview 套件 | ○ 走 REST,無現成範例 |
| R5 從 Lovable 轉換 | ○ React 元件原封搬、路由重寫,一到兩天 | ○ React 元件當島嵌入、頁面骨架重寫成 .astro,兩到三天 |
◎ 零成本 |
| R6 五年不變 | △ 使用率第一但滿意度 68%→55%,State of JS 2025 收到 303 則「過度複雜」抱怨;12→13→15→16 心智模型改過多次;靜態 export 是它的冷門子集,design-home 的 build.js 就是為了繞限制寫的 |
◎ State of JS 2025 滿意度第一(約 91%),大版本升級附 codemod;靜態輸出是預設不是繞出來的 | △ 2026-10-13 才 1.0,承諾 API 穩定,但生態最小、出事最難找答案 |
| R7 Cloudflare Pages | ○ 可以;要關 image 優化、不能有 API route、redirects 改寫成 _redirects |
◎ Astro 是 Cloudflare 旗下專案,第一公民 | ○ nitro 打包,可以 |
| R8 團隊與決策者 | ◎ 已上線一站、有 runbook、Payload 同一套;只多懂一種技術 | △ 要多懂一種;但它是「HTML 加一點 JS」,對不熟前端的人最容易讀懂 | △ 要多懂一種,而且是最新的那種 |
| R9 範圍小而靜態 | ○ 只用到 Next 的一小塊 | ◎ 正是它設計的目標場景 | △ 強項(型別路由、server functions)全用不到 |
怎麼讀這張表與最後的決定:
vite.config.ts 攤平、脫離 Lovable 包裝層。一次轉換曾派出(A1)並在中途停掉,工作區已還原。什麼情況會回頭改框架(反悔條件)
① TanStack Start 大版本不相容升級讓站起不來、或 nitro beta 出不了靜態檔而官方無修。② 官網長到上百頁、由非工程師頻繁改——Astro 的內容集合明顯好維護。③ 要與 design-home 共用 Payload 的程式碼(不只共用內容)——Next 直接贏。
目前三條都不成立。任一條成立時回來改這一節,並在 README 討論紀錄記推翻。
資料來源(2026-10 查證):TanStack Start 1.0 is Stable、State of JavaScript 2025: Meta-Frameworks、Astro vs Next.js for Static Sites: 2026 Corporate Website Stack Decision Guide、Astro vs Next.js (2026): Real Benchmarks、Astro 官方 Payload CMS 指南、What is TanStack Start (Cloudflare)。
guidant-ai-website repo,Lovable 拉回的版本)| 項目 | 現況 | 怎麼處理 |
|---|---|---|
| 框架 | TanStack Start 1.168+TanStack Router 檔案路由+React 19+Vite 8+nitro 3.0 beta,vite.config.ts 被 @lovable.dev/vite-tanstack-config 包一層 |
留。vite.config.ts 加 tanstackStart: { prerender: { enabled: true, crawlLinks: true } },build 出 .output/public/ 靜態檔 |
| 路由 | 中文在根層、英文在 /en/ 同構一份,共 34 支;每支只做 head+掛頁面元件 |
留。新頁照樣中英各一支;指南用 $slug 動態路由,prerender 時列出全部 slug |
| 頁面元件 | components/pages/:HomePage 113 行、ProductPage 119 行、StubPage 12 行 |
不動 |
| 全站元件 | components/site/:SiteHeader、SiteFooter、primitives(LLink、Logo、Placeholder、CTA) |
不動 |
| 文案 | i18n/zh-TW.json、en.json 各 429 行;元件不寫字串 |
不動;pageHead() 擴充 canonical、hreflang、og(見第四節) |
| 樣式 | styles.css 設計 token(oklch)、亮暗兩套、Tailwind 4 |
不動 |
| shadcn | components/ui/button.tsx |
不動 |
<head> |
pageHead() 已出 title、description、og:title、og:description、twitter:card |
在 pageHead() 的 links/meta 補 canonical、hreflang、og:image、og:url、og:locale |
robots.txt |
只放行 Googlebot、Bingbot、Twitterbot、facebookexternalhit 與 * |
直接改 public/robots.txt,抄 design-home robots.ts 的 AI 爬蟲清單 |
sitemap、JSON-LD、llms.txt、分析工具 |
無 | 內容抄 design-home;sitemap 用 build 腳本從路由清單產到 public/sitemap.xml,JSON-LD 放 head().scripts,llms.txt 放 public/ |
| Lovable 殘留 | lib/lovable-error-reporting.ts、server.ts 錯誤包裝、@lovable.dev/* 依賴 |
lovable-error-reporting 與 __lovableEvents 只在 Lovable 預覽有效,留著無害;想清就刪 |
估工:不用搬;開 prerender 半天,SEO 十項約三天。
// vite.config.ts(@lovable.dev/vite-tanstack-config 的 defineConfig 允許透傳 tanstackStart 設定)
export default defineConfig({
tanstackStart: {
server: { entry: "server" },
prerender: { enabled: true, crawlLinks: true, autoSubfolderIndex: true },
},
});npm run build 後 .output/public/ 下每個網址一支 index.html;動態路由(指南 $slug)要在 prerender.pages 或路由的 loader 可列舉時由 crawlLinks 從清單頁連結爬到。Cloudflare Pages 輸出目錄填 .output/public。
三個坑
① prerender 時 useI18n() 靠 useRouterState 取路徑,SSR 期間可用,不必改。② localStorage 主題腳本已在 __root.tsx 用 inline script,prerender 不會執行它,正確。③ .output/public/ 已含 public/ 的靜態檔(robots.txt、sitemap.xml、llms.txt、og 圖);sitemap 用 scripts/gen-sitemap.mjs 在 build 前從路由清單產,不手寫。
官網部署在 Cloudflare Pages,只上傳 .output/public/ 靜態檔,不用 Workers。
需求裡沒有任何一項需要伺服器:SEO/GEO 要的是 HTML 裡有內文,靜態檔是最強版本;Payload 改內容是觸發重 build,不是即時運算;表單是瀏覽器端打 BE。Workers 多出來的只有「每次請求即時算頁面」這一項能力,用不到卻多一層會出錯的程式、多一組 wrangler 設定、多一項帳單。
| 元件 | 放哪 | 備註 |
|---|---|---|
| 官網 | Cloudflare Pages,純靜態 | 連 GitLab repo,build 指令 npm run build,輸出目錄 .output/public,push 自動部署。與 design-home 的 WEB_BUILD_MODE=export 同一條路 |
| Payload CMS | 自架 VPS,Cloudflare Tunnel 對外 | 之後內容要搬進去時才起;存檔打 Pages Deploy Hook 重 build,約 1~2 分鐘生效 |
| 圖片、og 圖 | Cloudflare R2 | 與 design-home 同 |
| 表單、自家 API | 現有 Guidant BE | BE 對官網網域開 CORS;表單端點要有 rate limit 或驗證碼(公開可打)。也可比照 design-home 直送 Google Forms,不經 BE |
Pages 上誰能 call API:
| 誰在 call | 什麼時候 | 能不能 | 例子 |
|---|---|---|---|
| 使用者的瀏覽器 | 使用者操作時 | 能 | 送預約表單、聯絡表單 |
| build 機器 | npm run build 那一刻 |
能 | 從 Payload 抓文章、FAQ,寫進 HTML |
| Pages 伺服器 | 使用者每次開頁 | 不能 | 純靜態沒有伺服器。官網沒有「每個訪客看到不同內容」的頁,不需要 |
之後要轉 Workers 怎麼辦:nitro 本來就以 cloudflare 為預設目標,.output/server/ 已是 Workers 可跑的格式,關掉 prerender 改部署 server 即可,頁面程式碼不動。Pages Functions 等同把 Workers 塞回來,不用。
會需要轉 Workers 的只有兩種情況,目前都沒有:登入後才看得到或每個訪客不同的頁(官網不做登入、不做試用);編輯在 Payload 存檔前要即時預覽(design-home 也沒做,等重 build 就夠;真要做是另開一條預覽路由給編輯,官網本體不動)。
設定步驟照 design-home-fe/docs/runbooks/production-deploy.md Part B/C3(Pages)、C2(R2)、C1(Tunnel)。
| 配備 | 做法 | design-home 參考 |
|---|---|---|
| 每頁 title、description | 已有,確認每頁文案獨一無二、title 60 字內、description 150 字內 | apps/web 各 page.tsx 的 generateMetadata |
| canonical | 每頁 <link rel="canonical"> 指自己的正式網址(含網域、不含 query) |
同上 |
| hreflang | 中英各一套網址,每頁互指:zh-Hant、en、x-default 指中文 |
本站新寫 |
| Open Graph 圖 | 每頁或至少每區一張 1200×630 的 og:image,社群與 AI 引用時會抓 | 本站新寫 |
| sitemap.xml | prerender 自動產;確認含中英全部頁 | apps/web/src/app/sitemap.ts |
| robots.txt | 放行全部,附 Sitemap: 行 |
apps/web/src/app/robots.ts |
| 效能 | 靜態檔天生過關,LCP 目標 2.5 秒內;圖片走 WebP、標寬高 | 無 |
| 送件 | 上線後到 Google Search Console 與 Bing Webmaster Tools 驗證網域、送 sitemap | docs/ai-seo/local-presence-checklist.md |
AI 搜尋引擎抓網站的方式跟 Google 不同:不跑 JS、偏好一段就能摘出答案的文字、很依賴結構化資料與站外的一致訊號。四件事:
public/robots.txt)照 design-home 已查證過的清單,User-agent: * 已放行時另外明列是「明確表態」,部分爬蟲會先找針對自己的規則:
User-agent: GPTBot
User-agent: OAI-SearchBot
User-agent: ChatGPT-User
User-agent: ClaudeBot
User-agent: Claude-User
User-agent: Claude-SearchBot
User-agent: PerplexityBot
User-agent: Google-Extended
User-agent: Amazonbot
User-agent: CCBot
User-agent: meta-externalagent
Allow: /
User-agent: *
Allow: /
Sitemap: https://<官網網域>/sitemap.xml
參考:design-home-fe/apps/web/src/app/robots.ts(內含各家 UA 的查證註解,Claude-Web 是舊名已棄用)。
每頁 <head> 放一段 <script type="application/ld+json">。Guidant AI 是 B2B 軟體,型別與 design-home(室內設計、LocalBusiness)不同:
| 頁 | schema.org 型別 | 關鍵欄位 |
|---|---|---|
| 全站(放在 root) | Organization |
name 竣盟科技股份有限公司、url、logo、address、telephone、email、sameAs(LinkedIn、billows.tech) |
| 產品總覽、功能細頁 | SoftwareApplication |
name Guidant AI、applicationCategory BusinessApplication、operatingSystem、description、offers 不填(報價制) |
| 指南六篇 | Article |
headline、datePublished、dateModified、author(Organization)、inLanguage |
| 有問答的頁(解決方案、信任中心) | FAQPage |
mainEntity 每題一組 Question/Answer,答案第一句就是答案 |
| 每頁 | BreadcrumbList |
麵包屑,幫 AI 理解站的階層 |
參考:design-home-fe/apps/web/src/lib/jsonld.ts,注意它把 < 轉義成 < 的作法要照抄,避免文案含 </script> 把標籤提早關掉。
llms.txt站根放一份純文字 /llms.txt,給 AI 助理的網站導覽:一段公司與產品介紹、主要頁面清單(每行「標題、網址、一句說明」)、指南文章清單。prerender 後放 public/llms.txt 即可,內容改動時手動更新。
參考:design-home-fe/apps/web/src/app/llms.txt/route.ts(它從 CMS 動態產,本站先寫死)。
參考:design-home-fe/docs/ai-seo/faq-draft.md(FAQ 寫法原則)、local-presence-checklist.md(站外清單的結構,內容要換成 B2B 平台)。
| 要看什麼 | 工具 | 做法 |
|---|---|---|
| 流量、來源、停留、轉換 | GA4(或 Plausible、Cloudflare Web Analytics 等不用 cookie 同意的方案) | 在 root 加 script;預約展示與聯絡表單送出設成轉換事件 |
| Google 收錄、查詢詞、點閱率 | Google Search Console | 驗證網域、送 sitemap |
| Bing 收錄 | Bing Webmaster Tools | 同上,ChatGPT 搜尋的索引來源是 Bing |
| AI 搜尋帶來的流量 | GA4 自訂管道群組 | 把 referrer 含 chatgpt.com、perplexity.ai、claude.ai、copilot.microsoft.com、gemini.google.com 歸成「AI 搜尋」管道 |
| 對外投放與引用 | UTM 參數 | 所有對外貼的連結帶 utm_source/utm_medium,才分得出 AI 引用與自然搜尋 |
| 有沒有被 AI 引用 | 手動抽查 | 每月用 ChatGPT、Perplexity 問「CMMC 合規管理工具」「ISO 27001 稽核軟體 台灣」之類的題,看有沒有出現 Guidant AI |
開 prerender、部署後,用爬蟲身分抓原始 HTML,不是用瀏覽器看:
# 數字是 0 代表 AI 爬蟲看到的是空殼
curl -s -A "GPTBot" https://<官網網域>/ | grep -c "Guidant"
curl -s -A "ClaudeBot" https://<官網網域>/solutions/cmmc | grep -c "CMMC"
# 每一支路由都要有靜態檔(本機 build 後)
find out -name index.html | wc -l # 應等於 17 頁 × 2 語言 = 34(加指南 6 篇 × 2)
# 結構化資料驗證
# https://validator.schema.org/ 貼網址
# https://search.google.com/test/rich-results另外 robots.txt、sitemap.xml、llms.txt 三支用瀏覽器直接開要能看到內容。
| # | 做什麼 | 檔案 |
|---|---|---|
| 1 | 開 prerender、確認 .output/public/ 每頁一支 index.html(併入 B1 第一步) |
vite.config.ts |
| 2 | pageHead() 補 canonical、hreflang(zh-Hant/en/x-default)、og:url、og:image、og:locale |
src/i18n/index.ts、各 routes/**.tsx |
| 3 | public/robots.txt 放 AI 爬蟲清單與 Sitemap 行 |
public/robots.txt |
| 4 | sitemap 產生腳本,中英全頁 | scripts/gen-sitemap.mjs → public/sitemap.xml |
| 5 | JSON-LD 工具與五種型別,root 掛 Organization,各頁掛對應型別 | src/lib/jsonld.ts、__root.tsx、各路由 head() |
| 6 | 寫 llms.txt |
public/llms.txt |
| 7 | 每區一張 og:image(1200×630) | public/og/ |
| 8 | 接分析工具、設轉換事件、AI 搜尋管道群組 | __root.tsx、Cloudflare 後台 |
| 9 | Cloudflare Pages 專案連 GitLab repo,build npm run build,輸出 .output/public |
Cloudflare 後台 |
| 10 | 上線後 Search Console、Bing Webmaster 驗證與送 sitemap | 後台操作 |
| 11 | B2B 站外佈局清單(LinkedIn、G2/Capterra、合作夥伴連結)給決策者排程 | 本 FR 新文件 |
網址先用 VITE_SITE_URL 環境變數佔位(Vite 的公開變數要 VITE_ 前綴)。
公司內部(抄內容,寫法改 TanStack):~/Projects/Design-Home/design-home-fe/
| 檔 | 看什麼 |
|---|---|
apps/web/src/app/robots.ts |
AI 爬蟲 UA 清單與查證註解 |
apps/web/src/app/sitemap.ts |
sitemap 結構 |
apps/web/src/lib/jsonld.ts |
JSON-LD 工具、< 轉義 |
apps/web/src/app/llms.txt/route.ts |
llms.txt 內容骨架 |
apps/web/scripts/build.js |
產 _headers 的做法 |
apps/web/src/app/layout.tsx、(main)/*/page.tsx |
每頁 meta 要有哪些欄位(寫法不同,欄位照抄) |
apps/web/src/lib/payload.ts |
官網 build 時打 Payload API 的資料層(含租戶過濾),未來接 CMS 直接抄 |
docs/decisions/ADR-003、ADR-005 |
為什麼選純靜態+Cloudflare Pages+webhook 重 build |
docs/runbooks/production-deploy.md |
Cloudflare Pages、R2、Tunnel、Access 的設定步驟 |
docs/ai-seo/faq-draft.md、local-presence-checklist.md |
FAQ 寫法、站外佈局清單結構 |
apps/admin/src/hooks/revalidateWeb.ts |
Payload 改內容後打 Deploy Hook 重 build(未來接 CMS 用) |
外部:
head()(meta/links/scripts):https://tanstack.com/router/latest/docs/framework/react/guide/document-head-managementSoftwareApplication:https://schema.org/SoftwareApplication 、FAQPage:https://schema.org/FAQPage