下次建行銷官網照這份做——每一步做什麼、去哪做、會踩什麼坑、怎麼驗證做對了
給下一個要建官網、但不熟 Cloudflare 與 SEO 的人。每一節都是「要做什麼 → 去哪做 → 會踩什麼坑 → 怎麼驗」。範例值取自 Guidant AI 官網(程式碼在
~/Projects/Billows/Audit-Manager/guidant-ai-website),換站時把網域、專案名換掉即可。坑一律三段:症狀(你會看到什麼)→ 原因 → 修法與驗證。指令可直接複製跑;
$B代表站台網址(不帶結尾斜線),先export B=https://guidant-ai.stg.jedicotech.com。
骨架 → 內容頁 → SEO 配備 → 截圖 → 部署 → 網域 → 拿 noindex → 關掉 Cloudflare 預設攔截(含 Email 模糊化)→ 填 Web Analytics token → 搜尋引擎登記 → 分析 → 驗收
| # | 步驟 | 一句話 | 誰做 | 約多久 |
|---|---|---|---|---|
| 1 | 骨架 | 用 Lovable 生出版型後拉回自己的 git repo,之後不再回 Lovable 改 | 工程 | 半天 |
| 2 | 靜態輸出 | 開 prerender,每個網址產一支 index.html |
工程 | 半天 |
| 3 | 內容頁 | 文案進 i18n json/Markdown,元件不寫字串 | 工程 | 每 5 頁約 1 天 |
| 4 | SEO 配備 | head 五欄、robots、sitemap、JSON-LD、og 圖、llms.txt | 工程 | 2~3 天 |
| 5 | 截圖 | 從自家系統的 Demo 資料截亮暗兩套,壓 WebP | 工程 | 1~2 天(含建 Demo 資料) |
| 6 | 部署 | Cloudflare Pages 連 GitLab,push 即部署 | 決策者後台+工程 | 1 小時 |
| 7 | 網域 | Pages 綁自訂網域,VITE_SITE_URL 指它 |
決策者後台 | 15 分鐘 |
| 8 | 拿 noindex | 刪 _headers 的 X-Robots-Tag: noindex;會改 canonical 的修正(結尾斜線)要先上線 |
工程 | 10 分鐘 |
| 9 | 關 zone 層攔截 | AI 爬蟲政策、Bot Preference Sync、Email 模糊化三個開關 | 決策者後台 | 15 分鐘 |
| 10 | 填 Web Analytics token | 帳號層建站點、拿 token 填 Pages 環境變數、重新部署 | 決策者後台 | 15 分鐘 |
| 11 | 搜尋引擎登記 | Google Search Console 驗證+送 sitemap,Bing 一鍵匯入 | 有公司帳號的人 | 30 分鐘 |
| 12 | 分析 | 感謝頁當轉換、看 referrer、三個後台分工 | 決策者後台+工程 | 30 分鐘 |
| 13 | 驗收 | 用各家爬蟲真實身分抓原始 HTML、schema 驗證、Lighthouse | 工程 | 半天 |
三個最貴的教訓(後面各節細講):
-A GPTBot 會假綠。Google 會分兩輪:先讀 HTML,之後才排隊跑 JavaScript 渲染;GPTBot、ClaudeBot、PerplexityBot 這些 AI 爬蟲完全不跑 JavaScript。純 React/Vue SPA 給它們的是一個空的 <div id="root">,等於沒有網站。
所以框架要能「build 時把每頁先算好成 HTML」(這叫 prerender/靜態預產生)。TanStack Start、Next.js、Astro、Nuxt 都能做;本站用 TanStack Start,因為 Lovable 產出的骨架就是它,開 prerender 即可,零轉換成本。
vite.config.ts 被 @lovable.dev/vite-tanstack-config 包了一層,它已經內建 tanstackStart、react、tailwind、nitro 等 plugin。不要自己再加這些 plugin,會重複載入而壞掉;要加設定就塞進它的 defineConfig({ ... })。bun.lock 不必帶回來)。// vite.config.ts
import { defineConfig } from "@lovable.dev/vite-tanstack-config";
export default defineConfig({
nitro: { preset: "cloudflare-module" }, // 一定要固定,見下方坑
tanstackStart: {
server: { entry: "server" },
prerender: { enabled: true, crawlLinks: true, autoSubfolderIndex: true },
pages: [
...["/", "/en/", "/demo/thanks/", "/contact/thanks/", "/en/demo/thanks/", "/en/contact/thanks/"]
.map((path) => ({ path })),
{ path: "/404/", prerender: { outputPath: "/404.html" } },
{ path: "/en/404/", prerender: { outputPath: "/en/404.html" } },
],
},
});npm run build 後產物在 .output/public/,每個網址一支 index.html(autoSubfolderIndex: true → /product/index.html)。這個資料夾就是要部署的東西。crawlLinks: true:從首頁開始順著 <a href> 爬,爬到的頁都會產出。動態路由(例如指南 /resources/guides/$slug)只要清單頁有連到每一篇,就會自動被爬到,不必手列 slug。pages 明列。驗證:
cd ~/Projects/Billows/Audit-Manager/guidant-ai-website
npm run build
find .output/public -name index.html | wc -l # 應等於 頁數 × 語言數
ls .output/public/404.html .output/public/en/404.html
npx serve -l 5180 .output/public # 本機看靜態產物(不是 dev server)npm run build 正常,push 後 Cloudflare Pages 的 build log 每一頁 prerender 都 500,部署失敗。CF_PAGES=1,nitro 自動切成 cloudflare-pages preset,伺服器檔改放 dist/_worker.js,prerender 找不到 server 檔。vite.config.ts 固定 nitro: { preset: "cloudflare-module" }。本機模擬 build 機:CF_PAGES=1 npm run build && find .output/public -name index.html | wc -l # 數字要跟不帶 CF_PAGES 時一樣pages 之後首頁不見了pages: [...],build 完 .output/public/index.html 消失,全站只剩感謝頁。pages,prerender 就不再預設從 / 起爬。pages 裡一定要放 / 與 /en/(各語系首頁)。ls .output/public/index.html .output/public/en/index.html 都要在。/404/index.html,Pages 不認404.html;沒有就把首頁當 SPA 回退頁、回 200。src/routes/404.tsx(+en/404.tsx),在 pages 用 prerender: { outputPath: "/404.html" } 指定落檔位置;404 頁自己加 <meta name="robots" content="noindex">。curl -s -o /dev/null -w '%{http_code}\n' $B/nonexistent-page/ # 404Buffer is not defined。gray-matter 內部用 Node 的 Buffer。prerender 時跑在 Node 沒事,站內換頁時同一段程式跑在瀏覽器就壞。js-yaml 解析。文章用 Vite 的 import.meta.glob('/content/guides/*.md', { query: '?raw', import: 'default', eager: true }) 在 build 時讀進來。驗證方式是 headless 瀏覽器從清單頁點進文章、再點文內連結換篇,console 無錯。<head> 要有什麼| 欄位 | 範例 | 說明 |
|---|---|---|
title |
CMMC|Guidant AI(英文 CMMC | Guidant AI) |
60 字內、每頁不同 |
meta description |
該頁副標 | 150 字內 |
link rel=canonical |
https://guidant-ai.stg.jedicotech.com/solutions/cmmc/ |
告訴搜尋引擎「這頁的正式網址」,含網域、帶結尾斜線 |
link rel=alternate hreflang |
zh-TW/en/x-default 三筆 |
中英互指;x-default 指中文 |
og:title/og:description/og:url/og:image/og:locale |
og 圖要絕對網址+og:image:width 1200、og:image:height 630 |
社群分享卡與部分 AI 引用會抓 |
做法:寫一支共用函式(本站 src/i18n/index.ts 的 seoTags(pathname)),每支路由的 head() 只傳自己的實際路徑進去,網域從 VITE_SITE_URL 組。不要放在最外層 __root 的 head:TanStack 站內換頁不一定重跑最外層的 head,canonical 會停在上一頁。
| 檔 | 是什麼 | 本站腳本 |
|---|---|---|
robots.txt |
給爬蟲的進出規則。User-agent: * 已放行全部,另外明列 AI 爬蟲是「明確表態」(部分爬蟲先找針對自己的規則) |
scripts/gen-sitemap.mjs |
sitemap.xml |
全站網址清單,中英每筆帶三組 hreflang | 同上,掃 src/routes 與 content/guides 自動產 |
llms.txt |
給 AI 助理的網站導覽:一段公司介紹+每頁「標題、網址、一句說明」(規範見 llmstxt.org) | scripts/gen-llms.mjs |
llms-full.txt |
全站文案+指南原文攤平成純文字,AI 一次抓完 | scripts/gen-llms-full.mjs |
robots.txt 明列的 11 個 AI 爬蟲:GPTBot、OAI-SearchBot、ChatGPT-User、ClaudeBot、Claude-User、Claude-SearchBot、PerplexityBot、Google-Extended、Amazonbot、CCBot、meta-externalagent(Claude-Web 是已棄用舊名,不加)。
package.json 掛法:"prebuild": "node scripts/gen-sitemap.mjs && node scripts/gen-llms.mjs && node scripts/gen-llms-full.mjs"。頁清單由腳本掃路由檔產生,新加頁不用改腳本;某頁在 i18n 找不到標題說明時讓 build 直接中止,避免被靜默漏列。占位頁(隱私、條款「內容準備中」)從 llms 排除、sitemap 保留。
JSON-LD 是放在 <script type="application/ld+json"> 的一段 JSON,用 schema.org 的詞彙告訴搜尋引擎「這頁是什麼」。B2B 軟體官網用這五型:
| 型別 | 掛哪 | 關鍵欄位 |
|---|---|---|
Organization |
全站(root head) | name、alternateName(中英互指)、url、logo、PostalAddress、telephone、email、sameAs;@id 固定 <網域>/#organization,其他型別用 @id 指回 |
BreadcrumbList |
每頁 | 最後一層等於該頁 canonical;中間層必須是打得開的網址,沒有自己頁面的分組(如「解決方案」「資源」)不列 |
SoftwareApplication |
產品總覽、功能細頁 | applicationCategory BusinessApplication;報價制就不填 offers |
Article |
指南文章 | headline、datePublished、dateModified、author、inLanguage |
FAQPage |
有問答段的頁 | 每題一組 Question/Answer,答案第一句就是答案 |
公司資訊集中一支檔(本站 src/lib/site.ts 的 companyFor(lang)),英文頁用英文公司名與地址。
zh-Hant、sitemap 寫 zh-TW,Google 當成兩個不同語系。zh-TW)。JSON-LD 的 inLanguage 是另一件事,用 zh-Hant 沒問題。curl -s $B/product/ | /usr/bin/grep -ao 'hrefLang="[^"]*"' | sort -u
curl -s $B/sitemap.xml | /usr/bin/grep -ao 'hreflang="[^"]*"' | sort -u # 兩邊同一組值/product/index.html,Pages 對 /product 會 308 到 /product/。產網址的程式(sitemap、canonical、hreflang、og:url、llms、站內連結)若沒帶斜線,就和伺服器不一致。/(共用 withSlash()),TanStack Router 設 trailingSlash: "always" 讓站內 Link 自動補。curl -s $B/sitemap.xml | /usr/bin/grep -o '<loc>[^<]*' | sed 's/<loc>//' \
| while read u; do c=$(curl -s -o /dev/null -w '%{http_code}' "$u"); [ "$c" = 200 ] || echo "$c $u"; done # 無輸出才對og:image 指 PNG、絕對網址;webp 可留著。產圖腳本(本站 scripts/make-og.mjs)用 headless Chromium 截一張 HTML。curl -sI $B/og/home.png | /usr/bin/grep -i 'content-type' # image/png
curl -s $B/ | /usr/bin/grep -ao 'property="og:image" content="[^"]*"' # https://… .pngawait document.fonts.ready 並檢查 document.fonts.check('700 48px "Noto Sans TC"'),沒載到就丟錯。產完逐張開圖目視。<script> 提早關掉</script> 字樣,原樣塞進 <script> 會提早結束標籤(也是注入面)。JSON.stringify(data).replace(/</g, "\\u003c")(本站 src/lib/jsonld.ts 的 jsonLdScriptHtml)。把整頁原始碼貼到 https://validator.schema.org/ 要 0 錯。grep -c CMMC index.html 得 1 或被判成 binary。grep -c 數的是「行」,中文還可能讓 grep 判成 binary。sed 's/<[^>]*>//g' f.html | /usr/bin/grep -ao 'CMMC' | wc -l。VITE_SITE_URL(例:https://guidant-ai.stg.jedicotech.com)決定 canonical、hreflang、og:url、og:image、sitemap、robots 的 Sitemap 行、llms、JSON-LD 的 url 與 @id。換網域只改這一個值、重新 build。
.env.local(不入版控);Cloudflare Pages 寫在專案環境變數。throw new Error("缺 VITE_SITE_URL")),否則會默默產出 undefined/product。VITE_ 開頭的變數是 build 時寫死進 HTML,改了後台的值要重新部署才生效。上線前不想被收錄,用 public/_headers(Pages 讀這支檔加 HTTP 標頭):
/*
X-Robots-Tag: noindex
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
Referrer-Policy: strict-origin-when-cross-origin
Permissions-Policy: camera=(), microphone=(), geolocation=()
/og/*
Cache-Control: public, max-age=86400
noindex 只擋「收進搜尋結果」,不擋爬蟲讀內文——爬蟲驗收、結構化資料驗證、社群預覽卡都照樣能做。X-Robots-Tag: noindex 那一行(其餘安全標頭留著),push 部署。<meta name="robots" content="noindex"> 是刻意的,永遠保留。curl -sI $B/ | /usr/bin/grep -i x-robots-tag # 測試期有 noindex;開放後應無輸出同一個 Pages 專案綁多個網址(例如 stg 網址+正式網址+*.pages.dev),內容一模一樣,要避免被當重複內容。Cloudflare 官方文件寫明 _headers 支援絕對網址規則,可以依網域分開:
https://:project.pages.dev/*
X-Robots-Tag: noindex
https://:version.:project.pages.dev/*
X-Robots-Tag: noindex
https://guidant-ai.stg.jedicotech.com/*
X-Robots-Tag: noindex
本站尚未實測這個寫法;用之前先在 preview 部署跑下面兩行確認,兩個網址結果不同才算成功。若驗不過,退而用兩個 Pages 專案(同 repo、不同 production branch 或不同 _headers 產生方式)。另外 canonical 一律指正式網址,本身就會把權重集中過去。
curl -sI https://guidant-ai-website.pages.dev/ | /usr/bin/grep -i x-robots-tag # noindex
curl -sI https://<正式網域>/ | /usr/bin/grep -i x-robots-tag # 無輸出stg 會不會影響排名不會。Google 不看網址裡的字樣判斷是不是測試站,只看 noindex、內容與連結。用 stg 網址先開放收錄「試水溫」是可行的,代價在換網址那天(下一段)。
拿 noindex 的時機:會改 canonical 的修正(例如第 2 節的結尾斜線)要先上線,再拿 noindex。反過來的話,Google 會先收錄舊的(會被轉址的)網址,之後還要再轉址一輪才收斂。canonical 都穩了,測試網址(含 stg 字樣)直接開放收錄沒問題。
收錄與排名綁在網址上,不綁帳號也不綁網站內容。換網址時:
Cloudflare Pages 是 Cloudflare 的靜態網站託管:連 git repo,每次 push 自動 build 並部署到全球 CDN,免費額度對官網足夠。不需要 Workers(Workers 是每次請求即時跑程式,官網用不到)。
auditmanager/guidant-ai-website)。| 欄位 | 本站值 | 說明 |
|---|---|---|
| 專案名稱 | guidant-ai-website |
決定備用網址 guidant-ai-website.pages.dev |
| Production branch | feature/FR-139 |
push 到這支就部署正式;其他 branch 產 preview 網址 |
| Framework preset | None | 不用選 preset,自己填下面兩欄 |
| Build command | npm run build |
prebuild 會自動先跑 |
| Build output directory | .output/public |
不是 dist、不是 out |
| Root directory | 留空 |
VITE_SITE_URL build 失敗:| 變數 | 本站值 | 必填 |
|---|---|---|
VITE_SITE_URL |
https://guidant-ai.stg.jedicotech.com |
必填 |
NODE_VERSION |
22 |
必填(build 機預設 Node 版本可能太舊) |
VITE_CF_BEACON_TOKEN |
Web Analytics 的 token(第 7 節) | 要分析才填 |
VITE_FORM_KEY |
表單服務 key(例 Web3Forms) | 要表單寄信才填;留空時表單走 mailto 退路 |
專案 → 自訂網域(Custom domains)→ 設定自訂網域 → 輸入 guidant-ai.stg.jedicotech.com。
<專案名>.pages.dev),交給管 DNS 的人加。curl -s -o /dev/null -w '%{http_code}\n' https://guidant-ai-website.pages.dev/ # 200
curl -s -o /dev/null -w '%{http_code}\n' $B/ # 200
curl -s $B/ | /usr/bin/grep -ao 'rel="canonical" href="[^"]*"' # 從 pages.dev 打也該指正式網址zone 是 Cloudflare 對「一個網域」的管理單位(例如 jedicotech.com),底下所有子網域共用 zone 的安全設定。下面三個預設都在 zone 層,Pages 專案裡看不到、_headers 也改不動。網站掛到新的網域(新 zone)就要再關一次;同 zone 加子網域則不用。
curl $B/robots.txt 看到的不是你寫的,開頭多了 Content-Signal: search=yes, ai-train=no,還 Disallow 了 Amazonbot 等。curl -s $B/robots.txt | /usr/bin/grep -ic 'content-signal' # 0
curl -s $B/robots.txt | /usr/bin/grep -c '^User-agent:' # 等於自己寫的數量(本站 12)[email protected][email protected] 加一個 /cdn-cgi/l/email-protection 連結;爬蟲與不跑 JS 的使用者讀不到信箱;React 頁面還丟 hydration 錯誤(React #418:伺服器 HTML 與瀏覽器端渲染結果對不上)。對 CRA 法定的漏洞通報頁來說,信箱讀不到是合規問題。curl -s $B/trust/security/ | /usr/bin/grep -ac 'email-protection' # 0
curl -s $B/trust/security/ | /usr/bin/grep -ao 'security@[a-z.]*' # 看得到明文信箱新網域若在另一個 zone:上面三項全部重做一次。若在同一個 zone 只是新子網域:不用重做,但第 8 節腳本照樣對新網址跑一次。
Google 給網站主的後台:證明「這個網址是我的」之後,可以送 sitemap 請 Google 來抓、看收錄了幾頁、哪些查詢詞帶來曝光與點擊、有沒有抓取錯誤。不登記 Google 也會慢慢找到,登記是讓它快一點、並讓你看得到數據。
https://guidant-ai.stg.jedicotech.com/(含結尾斜線)。另一種「網域」資源要加 DNS TXT 紀錄、涵蓋整個網域所有子網域,需要管 DNS 的權限;只管一個網站時用網址前置字元較單純。<meta name="google-site-verification" content="…">。src/routes/__root.tsx 的 head().meta:{ name: "google-site-verification", content: "…" }),commit、push 部署。curl -s $B/ | /usr/bin/grep -ao 'google-site-verification" content="[^"]*"'這行 meta 要一直留著,拿掉會失去驗證。
驗證後左側 Sitemap(站點地圖)→ 填 sitemap.xml → 提交。當天「已找到的網址」多半是 0,幾天內會長出來。若站還在 noindex,報表會列「已排除:遭『noindex』標記排除」,這是預期結果。
curl -s -o /dev/null -A 'Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)' -w '%{http_code} %{content_type}\n' $B/sitemap.xml # 200 application/xml要即時確認,用頁面頂端的 網址檢查(URL Inspection)貼 sitemap 網址,按 測試線上網址(Test live URL)。
ChatGPT 的搜尋功能用 Bing 的索引,所以 Bing 對 AI 搜尋特別重要。
public/<key>.txt,內容就是 key 本身(本站 public/a42033575e8a46da956cfc2978fd63f0.txt)。POST https://api.indexnow.org/indexnow,JSON 欄位 host、key、keyLocation(key 檔的完整網址)、urlList(一次最多 10,000 筆)。本站用 sitemap 的全部 <loc>(48 筆)一次推:export B=https://guidant-ai.stg.jedicotech.com KEY=a42033575e8a46da956cfc2978fd63f0
urls=$(curl -s $B/sitemap.xml | /usr/bin/grep -ao '<loc>[^<]*' | sed 's/<loc>//' | python3 -c 'import sys,json;print(json.dumps(sys.stdin.read().split()))')
curl -s -o /dev/null -w '%{http_code}\n' -X POST https://api.indexnow.org/indexnow \
-H 'Content-Type: application/json' \
-d "{\"host\":\"${B#https://}\",\"key\":\"$KEY\",\"keyLocation\":\"$B/$KEY.txt\",\"urlList\":$urls}" # 202keyLocation 驗證你擁有這個網站,key 檔 404 或內容不等於 key,整批作廢,而且不會回頭通知你。curl -s -o /dev/null -w '%{http_code}\n' $B/$KEY.txt # 200
curl -s $B/$KEY.txt | grep -c "^$KEY$" # 1,內容等於 keyhttps://search.google.com/test/rich-results 要登入、沒有 API,只能手動貼:首頁、一篇指南、一個解決方案頁各一次。它和 schema.org validator 規則不完全相同,Google 對 FAQPage 的顯示資格另有限制(不一定會顯示成摺疊問答),以「沒有錯誤」為準。
data-cf-beacon 的 token。這個 token 是公開的,可以出現在 HTML;填進 Pages 環境變數時類型選「文字」(Text)即可,不必用「密碼」。jedicotech.com)那筆「自動設定」是另一個站的,不要混用它的 token。VITE_CF_BEACON_TOKEN,在 root head 只在「正式 build 且有 token」時載入 beacon:const token = import.meta.env["VITE_CF_BEACON_TOKEN"] as string | undefined;
const cfBeacon = import.meta.env.PROD && token
? [{ src: "https://static.cloudflareinsights.com/beacon.min.js", defer: true,
"data-cf-beacon": JSON.stringify({ token, spa: true }) }]
: [];spa: true 讓它攔截站內換頁,每次換頁算一次瀏覽。填完 token 要重新部署(VITE_ 變數是 build 時寫死的)。
curl -s $B/ | /usr/bin/grep -ao 'static.cloudflareinsights.com/beacon[^"]*' # 有輸出才算掛上Web Analytics 不支援自訂事件。做法是表單送出成功後導到真的感謝頁(/demo/thanks/、/contact/thanks/),後台看這兩頁的瀏覽數就是轉換數。感謝頁加頁內 noindex、不進 sitemap、在 prerender pages 明列。
history.pushState 到 /demo/thanks 想讓分析記一筆,結果畫面變 404。要做廣告投放(Google Ads 轉換追蹤、再行銷名單)、要細到按鈕點擊的自訂事件、要漏斗分析時才上 GA4。代價是要 cookie 同意橫幅(歐盟訪客)、設定複雜。純官網曝光與詢問量,Web Analytics+感謝頁就夠。
| 後台 | 位置 | 看的是什麼 | 視角 |
|---|---|---|---|
| AI Crawl Control → 網路安全(Security)清單 | zone 層左欄 | 每家 AI 爬蟲來了幾次、被放行還是封鎖 | 爬蟲 |
| Web Analytics 的 Referrers | 帳號層 Observability | 從 ChatGPT、Perplexity 等 AI 平台點連結進站的真人 | 人 |
| Search Console(以及 Bing 的 Sitemaps/AI Performance) | search.google.com/bing.com/webmasters | 搜尋引擎收錄了幾頁、哪些查詢詞帶來曝光 | Google/Bing |
| Bing Webmaster 的 IndexNow 頁 | bing.com/webmasters 左側 | 我們主動推送的網址(submitted URLs)與處理狀態 | 我們推給 Bing |
各後台互補不重疊:爬蟲來了不代表被引用,被引用不代表有人點,有人點才會出現在 Referrers。
沒有任何工具能直接告訴你「ChatGPT 推薦了你幾次」。只能拼三個間接訊號:
chatgpt.com、perplexity.ai、claude.ai、copilot.microsoft.com、gemini.google.com。對外貼的連結一律帶 utm_source/utm_medium,才分得出是自己投放還是自然引用。
curl -A GPTBot 這種裸名字它認不出來,就當一般訪客放行 → 你看到 200,真的 GPTBot 卻被 403。這就是「假綠」。<script> 與標籤,確認是內文裡有,不是 JSON-LD 裡有。| 名稱 | 用途 | UA 字串 |
|---|---|---|
| GPTBot | OpenAI 訓練 | Mozilla/5.0 AppleWebKit/537.36 (KHTML, like Gecko; compatible; GPTBot/1.1; +https://openai.com/gptbot) |
| OAI-SearchBot | ChatGPT 搜尋索引 | Mozilla/5.0 AppleWebKit/537.36 (KHTML, like Gecko; compatible; OAI-SearchBot/1.0; +https://openai.com/searchbot) |
| ChatGPT-User | 使用者叫 ChatGPT 開網頁 | Mozilla/5.0 AppleWebKit/537.36 (KHTML, like Gecko); compatible; ChatGPT-User/1.0; +https://openai.com/bot |
| ClaudeBot | Anthropic 訓練 | Mozilla/5.0 AppleWebKit/537.36 (KHTML, like Gecko; compatible; ClaudeBot/1.0; +claudebot@anthropic.com) |
| Claude-User | 使用者叫 Claude 開網頁 | Mozilla/5.0 AppleWebKit/537.36 (KHTML, like Gecko; compatible; Claude-User/1.0; +Claude-User@anthropic.com) |
| Claude-SearchBot | Claude 搜尋索引 | Mozilla/5.0 AppleWebKit/537.36 (KHTML, like Gecko; compatible; Claude-SearchBot/1.0; +Claude-SearchBot@anthropic.com) |
| PerplexityBot | Perplexity 索引 | Mozilla/5.0 AppleWebKit/537.36 (KHTML, like Gecko; compatible; PerplexityBot/1.0; +https://perplexity.ai/perplexitybot) |
| Amazonbot | Amazon(Alexa 等) | Mozilla/5.0 AppleWebKit/537.36 (KHTML, like Gecko; compatible; Amazonbot/0.1; +https://developer.amazon.com/support/amazonbot) Chrome/119.0.6045.214 Safari/537.36 |
| CCBot | Common Crawl(多家模型的訓練來源) | CCBot/2.0 (https://commoncrawl.org/faq/) |
| meta-externalagent | Meta AI | meta-externalagent/1.1 (+https://developers.facebook.com/docs/sharing/webmasters/crawler) |
| Googlebot | Google 搜尋 | Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html) |
| Bingbot | Bing 搜尋(ChatGPT 搜尋也靠它) | Mozilla/5.0 (compatible; bingbot/2.0; +http://www.bing.com/bingbot.htm) |
| Applebot | Apple(Siri、Spotlight) | Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 Safari/605.1.15 (Applebot/0.1; +http://www.apple.com/go/applebot) |
Google-Extended 不是獨立爬蟲(它是 robots.txt 裡控制 Gemini 訓練的代號,實際抓取用 Googlebot),所以不在測試清單。官方 UA 頁:OpenAI https://platform.openai.com/docs/bots 、Anthropic https://support.anthropic.com/en/articles/8896518 、Perplexity https://docs.perplexity.ai/guides/bots 。
存成 check-crawlers.sh,用法 bash check-crawlers.sh <站台網址> <路徑> <關鍵字>:
#!/usr/bin/env bash
# 例:bash check-crawlers.sh https://guidant-ai.stg.jedicotech.com /solutions/cmmc/ CMMC
B=${1:?站台網址}; P=${2:-/}; KW=${3:-Guidant}
GREP=/usr/bin/grep
while IFS='|' read -r name ua; do
[ -z "$name" ] && continue
body=$(curl -s -A "$ua" -w '\n%{http_code}' "$B$P")
code=${body##*$'\n'}; html=${body%$'\n'*}
n=$(printf '%s' "$html" | sed 's/<script[^>]*>[^<]*<\/script>//g; s/<[^>]*>//g' | $GREP -ao "$KW" | wc -l | tr -d ' ')
blocked=$(printf '%s' "$html" | head -c 300 | $GREP -aic 'blocked\|access denied')
printf '%-20s %s %s=%s %s\n' "$name" "$code" "$KW" "$n" "$([ "$blocked" != 0 ] && echo '← 被擋')"
done <<'UAS'
GPTBot|Mozilla/5.0 AppleWebKit/537.36 (KHTML, like Gecko; compatible; GPTBot/1.1; +https://openai.com/gptbot)
OAI-SearchBot|Mozilla/5.0 AppleWebKit/537.36 (KHTML, like Gecko; compatible; OAI-SearchBot/1.0; +https://openai.com/searchbot)
ChatGPT-User|Mozilla/5.0 AppleWebKit/537.36 (KHTML, like Gecko); compatible; ChatGPT-User/1.0; +https://openai.com/bot
ClaudeBot|Mozilla/5.0 AppleWebKit/537.36 (KHTML, like Gecko; compatible; ClaudeBot/1.0; +claudebot@anthropic.com)
Claude-User|Mozilla/5.0 AppleWebKit/537.36 (KHTML, like Gecko; compatible; Claude-User/1.0; +Claude-User@anthropic.com)
Claude-SearchBot|Mozilla/5.0 AppleWebKit/537.36 (KHTML, like Gecko; compatible; Claude-SearchBot/1.0; +Claude-SearchBot@anthropic.com)
PerplexityBot|Mozilla/5.0 AppleWebKit/537.36 (KHTML, like Gecko; compatible; PerplexityBot/1.0; +https://perplexity.ai/perplexitybot)
Amazonbot|Mozilla/5.0 AppleWebKit/537.36 (KHTML, like Gecko; compatible; Amazonbot/0.1; +https://developer.amazon.com/support/amazonbot) Chrome/119.0.6045.214 Safari/537.36
CCBot|CCBot/2.0 (https://commoncrawl.org/faq/)
meta-externalagent|meta-externalagent/1.1 (+https://developers.facebook.com/docs/sharing/webmasters/crawler)
Googlebot|Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)
Bingbot|Mozilla/5.0 (compatible; bingbot/2.0; +http://www.bing.com/bingbot.htm)
Applebot|Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 Safari/605.1.15 (Applebot/0.1; +http://www.apple.com/go/applebot)
UAS合格標準:13 行全部 200、關鍵字 > 0、沒有「← 被擋」。中英各挑一頁內容頁跑(例 /solutions/cmmc/、/en/solutions/cmmc/)。
存成 site-check.sh,用法 bash site-check.sh <站台網址>:
#!/usr/bin/env bash
B=${1:?站台網址,不帶結尾斜線}
G="/usr/bin/grep -a"
echo "== 標頭"
curl -sI "$B/" | $G -i 'x-robots-tag\|x-frame-options\|x-content-type'
echo "== robots.txt(數 User-agent;Content-Signal 應為 0)"
curl -s "$B/robots.txt" | $G -c '^User-agent:'
curl -s "$B/robots.txt" | $G -ic 'content-signal'
curl -s "$B/robots.txt" | $G '^Sitemap:'
echo "== sitemap 每筆狀態碼(只印非 200)"
urls=$(curl -s "$B/sitemap.xml" | $G -o '<loc>[^<]*' | sed 's/<loc>//')
echo "共 $(echo "$urls" | wc -l | tr -d ' ') 筆"
for u in $urls; do c=$(curl -s -o /dev/null -w '%{http_code}' "$u"); [ "$c" = 200 ] || echo "$c $u"; done
echo "== 首頁 head"
home=$(curl -s "$B/")
echo "$home" | $G -o 'rel="canonical" href="[^"]*"'
echo "$home" | $G -o 'hrefLang="[^"]*" href="[^"]*"'
echo "$home" | $G -o 'property="og:image" content="[^"]*"'
echo "JSON-LD 段數:$(echo "$home" | $G -o 'application/ld+json' | wc -l | tr -d ' ')"
echo "== 404"
curl -s -o /dev/null -w '/nonexistent-page/ → %{http_code}(應 404)\n' "$B/nonexistent-page/"
echo "== email 沒被 Cloudflare 改寫(應 0)"
curl -s "$B/" "$B/contact/" | $G -c 'email-protection'
echo "== llms"
curl -s -o /dev/null -w 'llms.txt %{http_code}\n' "$B/llms.txt"
curl -s -o /dev/null -w 'llms-full.txt %{http_code}\n' "$B/llms-full.txt"| 驗什麼 | 怎麼驗 |
|---|---|
| 結構化資料 | https://validator.schema.org/ 貼網址或整頁原始碼,0 錯 0 警告;Google 版見第 6 節 |
| 社群預覽卡 | 用平台爬蟲 UA 抓 og 標籤(下方指令);要看實際渲染再登入 Facebook Sharing Debugger、LinkedIn Post Inspector |
| 效能與無障礙 | Lighthouse(npx lighthouse $B/ --only-categories=performance,seo,accessibility --form-factor=mobile --chrome-flags=--headless),目標手機 Performance ≥ 85;noindex 期間 SEO 分數只扣 is-crawlable 一項是正常 |
| 手機版不爆版 | headless 390 寬開每頁,document.documentElement.scrollWidth - innerWidth 要 0 |
for ua in 'facebookexternalhit/1.1 (+http://www.facebook.com/externalhit_uatext.php)' 'LinkedInBot/1.0 (compatible; Mozilla/5.0; +http://www.linkedin.com)' 'Twitterbot/1.0' 'Slackbot-LinkExpanding 1.0 (+https://api.slack.com/robots)'; do
curl -s -A "$ua" $B/ | /usr/bin/grep -ao 'property="og:\(title\|image\)" content="[^"]*"' | head -2; echo; donelcp-lazy-loaded 不及格。loading="eager";手機量測抓到的最大元素是 loading="lazy" 的那張。loading="eager"+fetchpriority="high"。同時檢查標題層級不跳級(h1 後直接 h3 會扣無障礙分)。重跑 Lighthouse 手機版確認。多個人或多個 Claude session 同時在同一個 repo 資料夾改不同頁,最容易出事的是共用檔:src/i18n/zh-TW.json、en.json、SiteHeader.tsx、src/i18n/index.ts、vite.config.ts。
git add 帶走別人的改動git add <檔名> 會整檔帶走。git status --short # 先看有沒有不是自己的改動
git diff src/i18n/zh-TW.json # add 共用檔前一定看
git add -p src/i18n/zh-TW.json # 有別人的 hunk 就逐段挑,只 y 自己的
git diff --cached --stat # commit 前再看一次 staged 範圍禁止 git add -A、git commit -am。
npm run build 在別人的元件報 undefined.map。git worktree add ../site-verify HEAD,把自己的檔複製過去 npm install && npm run build,驗完 git worktree remove ../site-verify。headFrom(),把加 canonical 的人寫的 seoTags() 呼叫拿掉,全站 canonical 消失。git log -3 -- <檔> 看最近誰動過、先 pull 到最新;commit 前 build 一次並確認別人的成果還在:for f in $(find .output/public -name index.html); do /usr/bin/grep -aq 'rel="canonical"' "$f" || echo "MISSING $f"; donesed -E -i 產生怪檔sed -i -E 's/…//' *.md,內容沒變,資料夾多出一堆 *.md-E。-i 後面一個參數是備份檔副檔名,-E 被吃掉當副檔名。sed -i '' -E 's/…//' file,或改用 python/perl(perl -pi -e 's/…//' file)。改完 git status 確認沒有多出 -E 結尾的檔。grep -q canonical index.html 全部失敗,驗收迴圈把每頁都印成 MISSING。grep 包成函式/別名,加了會把含特殊字元的單行大檔判成 binary 的選項。/usr/bin/grep -a。type grep 可看出是不是被包過。git checkout -- .+git clean 之後還有殘留。git add 的 rename 不受 checkout -- . 影響。git status 看清楚、必要時先問人。真的要整個回到某個 commit 才用 git reset --hard <commit>。從測試網址(或試水溫網址)換到正式網址,照順序做。打勾前每項都跑對應的驗證指令。
A. 程式(工程,一個 commit)
B. Cloudflare 後台(決策者)
C. 線上驗證(工程)
export B=https://<正式網域>
curl -sI $B/ | /usr/bin/grep -ic x-robots-tag # 0
curl -s $B/sitemap.xml | /usr/bin/grep -c '<loc>' # 等於頁數 × 語言數
curl -s $B/sitemap.xml | /usr/bin/grep -c 'stg\.' # 0,舊網域沒殘留
curl -s $B/product/ | /usr/bin/grep -ao 'rel="canonical" href="[^"]*"' # 新網域、結尾 /
curl -s -o /dev/null -w '%{http_code} %{redirect_url}\n' https://<舊網域>/product/ # 301 → 新網域同路徑
bash check-crawlers.sh $B /solutions/cmmc/ CMMC # 13 行全 200
bash site-check.sh $BD. 搜尋引擎(有帳號的人)
E. 站外訊號(行銷,正式網址定了才做)
AI 搜尋判斷「推不推薦你」很看別人怎麼說你。公司名、地址、電話、產品名在各平台一字不差:
F. 上線一週後