Skip to content

TTFB 與 FCP:伺服器回應與首次繪製的優化

TTFB 與 FCP:伺服器回應與首次繪製

這兩個是 輔助指標,不列入 三大核心指標 的評分,但它們決定了核心指標的上限。

指標量什麼建議門檻
TTFB請求發出到收到第一個位元組≤ 800 毫秒
FCP畫面第一次出現任何內容≤ 1.8 秒

TTFB:伺服器多快開始回話

TTFB(Time to First Byte,第一個位元組時間)是請求送出到收到回應 第一個位元組 的時間,量的是伺服器多快開始回話,還沒談到畫面上出現什麼。

TTFB 是所有載入指標的天花板

TTFB 有 1 秒,LCP 就不可能低於 1 秒,內容還沒開始傳,怎麼繪製?

所以它雖然不被直接評分,卻是一切的起點。

TTFB 的六段組成

TTFB 不是單一動作,而是六件事的總和:

階段做什麼慢的原因
1. 轉址若有轉址,先跑一輪轉址鏈
2. DNS 查詢網域轉成 IPDNS 供應商慢、沒有預先解析
3. 建立連線TCP 三次握手實體距離遠
4. TLS 交握HTTPS 的加密協商憑證鏈太長、不支援連線恢復
5. 伺服器處理查資料、組裝回應通常是主因
6. 回應傳輸第一個位元組送達頻寬不足

第 1 段最容易被忽略

httphttps、非 www 轉 www,每一次轉址都是完整的一輪往返。兩次轉址串起來,TTFB 直接多幾百毫秒。

修法是 一跳到底:讓所有變體直接轉到最終網址,不要串接。做法見 轉址與 HTTP 狀態碼

怎麼量測各段

js
// 從導覽計時取出各階段的耗時
const [nav] = performance.getEntriesByType('navigation');

console.log({
  轉址: nav.redirectEnd - nav.redirectStart,
  DNS: nav.domainLookupEnd - nav.domainLookupStart,
  連線: nav.connectEnd - nav.connectStart,
  TLS: nav.connectEnd - nav.secureConnectionStart,
  伺服器處理: nav.responseStart - nav.requestStart,
  TTFB: nav.responseStart,
});

伺服器處理那一段最值得先看

上面的 responseStart - requestStart 就是伺服器真正花的時間。如果它佔了 TTFB 的大半,優化方向就在後端而不是網路。

DevTools 的 Network 面板點開任一請求的 Timing 分頁也看得到同樣的拆解。

縮短 TTFB 的手法

改用靜態產生

這是最有效的一招

靜態產生 的頁面在建置時就準備好了,伺服器只需要把檔案送出去,第 5 段幾乎歸零。

內容不常變動的網站(文件站、官網、部落格)沒有理由不這樣做。

渲染模式第 5 段的耗時
靜態產生(SSG)幾乎為零
增量靜態再生(ISR)命中快取時幾乎為零
用戶端渲染(CSR)幾乎為零(但內容要等程式碼)
伺服器端渲染(SSR)要查資料與組裝

SSR 的 TTFB 本來就比較慢

這不是設定錯誤。伺服器要查資料庫、跑模板、組裝 HTML 才能回傳第一個位元組。

SSR 買到的是「首屏內容早點出現」與「爬蟲讀得到」,代價就是 TTFB。取捨見 渲染模式怎麼選

用 CDN

CDN 把資源放在離使用者近的節點,縮短第 3、4、6 段。

資源類型CDN 的效果
靜態資源(圖片、CSS、JS)幾乎一定有效
靜態 HTML有效
動態頁面要看有沒有邊緣快取

單純代理不會加快伺服器處理

如果 CDN 只是把請求轉回源伺服器,第 5 段完全沒改善,只縮短了網路距離。

動態頁面要靠 邊緣快取:讓 CDN 節點自己保存一份回應,命中時不必回源。

設定快取標頭

# 帶版本號或雜湊的靜態資源:可以快取很久
Cache-Control: public, max-age=31536000, immutable

# HTML:不要長期快取,但允許先用舊的再背景更新
Cache-Control: public, max-age=0, must-revalidate

# 或用 stale-while-revalidate 讓重複造訪幾乎沒有等待
Cache-Control: public, max-age=60, stale-while-revalidate=86400
指令作用
max-age快取多久(秒)
immutable保證內容不會變,瀏覽器不必重新驗證
must-revalidate過期後必須向伺服器確認
stale-while-revalidate過期後先用舊的,背景更新

immutable 要搭配檔名雜湊

app.a1b2c3.js 這種帶雜湊的檔名,內容變了檔名也變,所以可以放心設一年的快取。

沒有雜湊的 app.js 設了長快取就很難更新,使用者可能一年都拿到舊版。建置工具通常會自動處理檔名雜湊。

減少轉址

nginx
# 三跳:http → https → www → 最終
# 改成一跳到底
server {
  listen 80;
  server_name example.com www.example.com;
  return 301 https://example.com$request_uri;
}

server {
  listen 443 ssl;
  server_name www.example.com;
  return 301 https://example.com$request_uri;
}

後端的最佳化

第 5 段的優化屬於後端範圍,但工程師該知道方向:

手法說明
資料庫查詢加索引最常見的瓶頸
避免 N+1 查詢一次取回關聯資料
加一層應用層快取Redis 之類的記憶體快取
非必要的資料延後取首屏用不到的資料改成之後再要
串流回應先送檔頭,內容邊算邊送

串流回應能顯著改善 TTFB

支援串流的框架可以 先把 <head> 送出去(讓瀏覽器提早開始下載 CSS 與字型),內容部分邊算邊送。

這樣 TTFB 大幅縮短,而總時間不變,但使用者感受到的速度明顯改善。

FCP:畫面第一次出現內容

FCP(First Contentful Paint,首次內容繪製)是畫面上 第一次出現任何內容 的時間,一段文字、一個圖示、一個色塊都算。

FCPLCP
量什麼第一次出現 任何 內容主要 內容繪製完成
建議門檻≤ 1.8 秒≤ 2.5 秒
算核心指標

FCP 是 LCP 的下限

FCP 之前不可能有 LCP,什麼都還沒畫,怎麼會有最大元素?

所以改善 FCP 通常會 帶動 LCP。兩者的優化手法也高度重疊。

改善 FCP 的手法

手法為什麼有效
縮短 TTFBFCP 的起點就是 TTFB
移除 阻塞渲染的資源CSS 沒解析完什麼都不會畫
首屏樣式內嵌不必等外部 CSS
字型加 font-display: swap文字不必等字型
preconnect提早建立第三方連線
html
<!-- 首屏樣式內嵌,其餘非同步 -->
<style>
  /* 只放首屏需要的 */
  body { margin: 0; font-family: system-ui, sans-serif; }
  .hero { min-height: 400px; }
</style>
<link rel="stylesheet" href="/styles.css" media="print" onload="this.media='all'">

這個站的做法

這是一個 靜態產生 的文件站,所以 TTFB 的第 5 段幾乎為零,建置時就把 HTML 準備好了:

bash
# 建置產出純靜態檔案
npm run build
決定對 TTFB 的影響
靜態產生而非 SSR伺服器處理時間趨近零
橫幅用 SVG(幾 KB)第 6 段的傳輸量小
不用網頁字型少一輪字型請求
樣式由建置工具打包並加雜湊可設長快取

檢查清單

項目標準
TTFB 在 800 毫秒內目標
FCP 在 1.8 秒內目標
沒有轉址鏈(一跳到底)必備
靜態資源走 CDN建議
帶雜湊的資源設長快取加 immutable建議
HTML 沒有設長快取必備
阻塞渲染的資源已處理建議
內容不常變的網站用靜態產生建議
已用導覽計時拆解各段建議

常見問題

TTFB 算核心指標嗎?

不算,它是輔助指標。但它是所有載入指標的起點,TTFB 有一秒,最大內容繪製 就不可能低於一秒。所以它雖然不被直接評分,卻決定了另一個指標的天花板。

合理的門檻是多少?

TTFB 建議在 800 毫秒內,FCP 建議在 1.8 秒內。這兩個都是輔助指標的參考值,不像三大核心指標有明確的評級門檻。

為什麼伺服器端渲染的 TTFB 反而變差?

因為伺服器要查資料、組裝 HTML 才能回傳第一個位元組,而純靜態檔案是直接送出。伺服器端渲染買到的是首屏內容早點出現與可被爬取,不是全面更快,取捨見 渲染模式怎麼選

FCP 和 LCP 差在哪?

FCP 是畫面第一次出現任何內容的時間,可能只是一段文字或一個色塊;LCP 是主要內容繪製完成的時間。前者是後者的下限,改善前者通常也會帶動後者。

CDN 一定能改善 TTFB 嗎?

對靜態資源幾乎一定有效,對動態頁面則要看有沒有做邊緣快取。單純把請求代理回源伺服器的設定不會加快伺服器的處理時間,只縮短了網路距離那一段。

延伸閱讀

參考資料:web.dev:Time to First Byte (TTFB)