AIEO/SEO/速度:邊緣層設定指南
結論先說:內容寫得好不等於被讀到。AI 抓取器不會等前端渲染完成,所以要讓內容被 ChatGPT、Perplexity、Gemini 正確引用,關鍵不在多寫幾個標籤,而在邊緣層先把渲染好的完整 HTML 交到機器人手上。這一頁完整記錄本站的做法,也歡迎其他醫師與架站人員直接套用。
Why
為什麼一定要有邊緣層
結論先說:本站是前端渲染(SPA),伺服器回給爬蟲的初始 HTML 只有一個空容器,正文是瀏覽器執行 JavaScript 後才長出來的。 Googlebot 有能力執行 JavaScript,但多數 AI 抓取器(GPTBot、PerplexityBot、ClaudeBot 等)不會等你渲染完——它們讀到空殼就走了。 這不是內容不好,而是內容根本沒被看到。
- ・AI 引擎抓不到正文:無法被摘要、無法被引用,等於在 AI 搜尋時代隱形。
- ・結構化資料被低估:JSON-LD 寫得再完整,若機器人拿到的是空殼頁,價值大幅折損。
- ・平台端無法根治:Base44 只提供前端渲染,也不允許自訂 HTTP 標頭與 /.well-known 檔案;這一層必須由 Cloudflare 補。
Architecture
兩層分工:誰做什麼,不重複
Base44 平台端(已完成)
- ・每頁獨立 canonical、title、description
- ・JSON-LD:Person/Physician、Article+citation、FAQPage
- ・文章網址採穩定 slug,不綁資料庫 ID
- ・動態產生 sitemap.xml、llms.txt、ai.txt
- ・文章以 Markdown 結構儲存、可一鍵匯出備份
Cloudflare 邊緣端(你要設定)
- ・機器人偵測與預先渲染,回傳含正文的 HTML
- ・把 /llms.txt、/ai.txt、/sitemap.xml 導到動態端點
- ・邊緣快取與靜態資源長快取
- ・CSP 等安全標頭、security.txt
- ・圖片 WebP、Brotli、HTTP/3 等加速選項
Data Flow
請求進來之後發生什麼
STEP 1
邊緣判斷來源
讀 User-Agent,區分 AI 抓取器/搜尋引擎與一般讀者。
STEP 2
機器人走預渲染
呼叫無頭瀏覽器取得渲染後 HTML,含完整正文與 JSON-LD。
STEP 3
結果進邊緣快取
同一網址 24 小時內直接命中,不重複渲染、成本極低。
STEP 4
讀者原樣轉送
真人仍走原本的 SPA 體驗,互動與登入完全不受影響。
注意:預渲染只在「內容一致」的前提下是正當做法——回給機器人的內容必須與讀者看到的相同,否則屬於偽裝(cloaking),會被搜尋引擎懲罰。 本方案回傳的是同一頁渲染後的結果,內容一致。
Step 1
部署 Worker:AI 索引檔與預先渲染
- ・Cloudflare 控制台 → Workers & Pages → Create Worker,貼上下方程式碼。
- ・Settings → Variables:新增 CF_ACCOUNT_ID 與 CF_API_TOKEN(Browser Rendering 權限)。
- ・Settings → Domains & Routes:加上 drkao.org/* 與 www.drkao.org/*。
- ・文章頁與主要頁面走 B-1 的後端鏡像(零成本、含文末互指連結),不需要 Browser Rendering 也能生效;CF_ACCOUNT_ID 與 CF_API_TOKEN 只影響 B-2 的其餘頁面,可暫時留空。
// Cloudflare Worker:drkao.org 邊緣層
// 1) 把 AI 索引檔導到 Base44 動態端點 2) 機器人請求走預先渲染 3) 其餘原樣轉送
const APP = "https://drkao.base44.app";
const DOCS = {
"/llms.txt": "llms",
"/ai.txt": "ai",
"/sitemap.xml": "sitemap",
};
const BOT_RE = /(GPTBot|OAI-SearchBot|ChatGPT-User|PerplexityBot|Perplexity-User|Google-Extended|Googlebot|Bingbot|ClaudeBot|Claude-Web|anthropic-ai|Applebot|Applebot-Extended|Amazonbot|CCBot|YouBot|facebookexternalhit|Twitterbot|LinkedInBot|Slackbot)/i;
export default {
async fetch(request, env, ctx) {
const url = new URL(request.url);
// A. AI 索引檔:由後端動態產生,內容永遠與文章同步
const doc = DOCS[url.pathname];
if (doc) {
const upstream = APP + "/functions/aiFeed?doc=" + doc;
const res = await fetch(upstream, { cf: { cacheTtl: 1800, cacheEverything: true } });
const headers = new Headers(res.headers);
headers.set("Cache-Control", "public, max-age=1800");
return new Response(res.body, { status: res.status, headers });
}
// B. 機器人:回傳預先渲染的完整 HTML(含正文、互指連結與結構化資料)
const ua = request.headers.get("user-agent") || "";
if (request.method === "GET" && BOT_RE.test(ua)) {
const cache = caches.default;
const key = new Request(url.toString(), { method: "GET" });
const hit = await cache.match(key);
if (hit) return hit;
// B-1. 文章與主要頁面:優先用後端 pageMirror(零成本、含 peer_links 互指連結)
const mirrored = await fetchMirror(url.pathname);
if (mirrored) {
const res = new Response(mirrored, {
headers: {
"Content-Type": "text/html; charset=utf-8",
"Cache-Control": "public, max-age=86400",
"X-Prerender": "mirror",
},
});
ctx.waitUntil(cache.put(key, res.clone()));
return res;
}
// B-2. 其餘頁面:退回無頭瀏覽器渲染(需 Workers 付費方案)
const rendered = await renderWithBrowser(url.toString(), env);
if (rendered) {
const res = new Response(rendered, {
headers: {
"Content-Type": "text/html; charset=utf-8",
"Cache-Control": "public, max-age=86400",
"X-Prerender": "1",
},
});
ctx.waitUntil(cache.put(key, res.clone()));
return res;
}
}
// C. 一般讀者:原樣轉送給 Base44
return fetch(new Request(APP + url.pathname + url.search, request));
},
};
// 從後端純文字鏡像端點取得該路徑的完整 HTML(含 peer_links 互指連結)
// mode=edge 表示由邊緣層在正式網址上輸出,此時鏡像頁不會加 noindex
async function fetchMirror(pathname) {
try {
const upstream =
APP + "/functions/pageMirror?mode=edge&path=" + encodeURIComponent(pathname);
const res = await fetch(upstream, { cf: { cacheTtl: 3600, cacheEverything: true } });
if (!res.ok) return null; // 404 表示此路徑無鏡像,交給 B-2
return await res.text();
} catch (e) {
return null;
}
}
// 用 Cloudflare Browser Rendering(需綁定 BROWSER)取得渲染後 HTML
async function renderWithBrowser(target, env) {
try {
const res = await fetch(
"https://api.cloudflare.com/client/v4/accounts/" + env.CF_ACCOUNT_ID + "/browser-rendering/content",
{
method: "POST",
headers: {
Authorization: "Bearer " + env.CF_API_TOKEN,
"Content-Type": "application/json",
},
body: JSON.stringify({ url: target, waitUntil: "networkidle0" }),
}
);
const data = await res.json();
return data?.result || null;
} catch (e) {
return null; // 渲染失敗就退回一般轉送,不讓爬蟲拿到錯誤頁
}
}curl -A "GPTBot" https://drkao.org/sanctuary/physician-risk-assessment | grep -i "drhao33"
# 若尚未部署 Worker,可先直接測後端鏡像:
curl "https://drkao.base44.app/functions/pageMirror?mode=edge&path=/sanctuary/physician-risk-assessment"Step 2
驗證 llms.txt 與 ai.txt
這兩個檔案由本站後端動態產生,內容會自動跟著文章更新,不需手動維護。llms.txt 告訴 AI 引擎「哪些是本站權威內容、該怎麼引用」; ai.txt 宣告抓取與引用政策並要求標註來源。sitemap.xml 同樣改為動態,會自動排除會員專屬文章。
https://drkao.org/llms.txt
https://drkao.org/ai.txt
https://drkao.org/sitemap.xml
# 未設 Worker 前,可先直接檢查後端來源:
https://drkao.base44.app/functions/aiFeed?doc=llms
https://drkao.base44.app/functions/aiFeed?doc=ai
https://drkao.base44.app/functions/aiFeed?doc=sitemap上線後記得到 Google Search Console 與 Bing Webmaster Tools 重新提交 sitemap 網址。
Step 3
快取與速度設定
# Cache Rules(Caching → Cache Rules)
# 規則名稱:Static assets long cache
當 URI Path 符合 /assets/* 或 副檔名 in (js, css, woff2, png, jpg, webp, svg)
→ Cache eligibility: Eligible for cache
→ Edge TTL: 1 month
→ Browser TTL: 1 week
# 規則名稱:HTML short edge cache
當 URI Path 不符合 /functions/* 且 不符合 /account* 且 不符合 /admin*
→ Cache eligibility: Eligible for cache
→ Edge TTL: 2 hours(Respect origin 關閉)
→ Browser TTL: 4 hoursSpeed → Optimization
✅ Auto Minify(JS / CSS / HTML)
✅ Brotli 壓縮
✅ Early Hints
✅ HTTP/3(with QUIC)
✅ 0-RTT Connection Resumption
Speed → Image Optimization
✅ Polish:Lossy + WebP
Network
✅ Tiered Cache(Argo Smart Routing 為付費加值,可選)- ・不要快取的路徑:/functions/*、/account*、/admin* 必須排除,否則會員與後台資料可能被跨使用者快取。
- ・發文後清快取:Caching → Configuration → Purge Everything,或用 API 針對該篇文章網址做單一清除。
- ・字體:本站已用 display=swap 與 preconnect;若要再快,可把字體自架並做繁中子集化。
Step 4
安全標頭與 security.txt
- ・Rules → Transform Rules → Modify Response Header:新增 Content-Security-Policy,內容沿用 index.html 中的那份策略(HTTP 標頭優先於 meta)。
- ・同一處可補上 Permissions-Policy 與 Cross-Origin-Opener-Policy。
- ・security.txt:Cloudflare 的 Security → Settings 內建產生器,或在上方 Worker 的 DOCS 表加一筆自訂回應。
- ・DNSSEC 可直接在註冊商開啟;CAA 暫緩,避免擋掉 Universal SSL 續簽。
Step 5
長期維運:可引用、可搬遷、少維護
- ・內容可長期被引用:每篇文章有穩定 slug 與自身 canonical,並附正式引用格式;外部引用不會因改版而失效。
- ・降低平台依賴:文章以標準 Markdown 儲存,可一鍵匯出成 .md 檔+網址對應表;換平台時網址結構照舊,連結不斷裂。
- ・維護成本低:sitemap/llms.txt/ai.txt 全部動態生成,新增文章不需要改任何設定檔。
- ・建議節奏:每月看一次 Search Console 的收錄數與 Core Web Vitals;每季匯出一次內容備份。