頁面一慢,第一個被怪罪的往往是圖片。但在互動型網站上,真正拖垮速度的常常是臃腫的 JavaScript,其次是阻擋畫面的 CSS,再來是伺服器回應得太慢。這篇把對速度影響最大的三件事講清楚:怎麼減少與延後 JavaScript 和 CSS、怎麼把伺服器回應時間也就是 TTFB 壓下來,以及這些調整最後如何反映在核心體驗指標與使用者的真實體感上。把順序抓對,你會發現多數速度問題不必砍功能也能解。

1. 先看懂 JS 和 CSS 為什麼會拖慢你的頁面

很多人以為頁面慢是圖片太大,但在互動型網站上,臃腫的 JavaScript 往往才是主因。要優化之前,得先理解瀏覽器是怎麼被這些資源卡住的,否則很容易把力氣花在感覺上該優化、實際影響卻不大的地方。

1.1 阻塞渲染的資源是什麼

瀏覽器解析 HTML 時,遇到沒有特別標記的 <script> 會停下來先下載並執行它,遇到 CSS 也要等樣式表下載完才能繼續繪製。這些會打斷繪製流程的資源,就是所謂阻塞渲染的資源。它們越多、越大,使用者盯著白畫面的時間就越長。換句話說,第一眼畫面要多久出現,很大程度取決於你在它前面擺了多少這種擋路的資源。

這裡有個關鍵的觀念要先建立:瀏覽器畫出第一眼畫面的過程,比較像一條流水線,而不是同時並行的一堆動作。HTML 從上往下讀,讀到一個擋路的腳本或樣式,整條線就得停下來等它處理完。所以同樣大小的資源,擺在頁面頂端和擺在底部,對首次繪製的影響天差地遠。我們診斷一個慢頁面時,第一件事往往不是看資源總量有多大,而是看有哪些資源卡在最前面、擋住了畫面。把擋路的搬開,常常比費力去壓縮每個檔案更快見效。

1.2 主執行緒被佔滿的代價

瀏覽器處理版面、繪製與使用者互動,靠的是同一條主執行緒。當一大包 JavaScript 在執行時,主執行緒被佔住,使用者這時點按鈕、捲頁面都得排隊等待。這種「畫面出來了卻按不動」的卡頓,是很多互動指標不理想的根源。畫面看起來好了,其實還沒真的能用,這中間的落差,正是使用者最容易感到煩躁的地方。

這種卡頓在手機上特別明顯,因為行動裝置的運算能力通常不如桌機,同樣一包 JavaScript 在手機上要花更久才跑得完。我們看過不少網站在桌機上測起來很順,一到中階手機就卡得使用者想關掉頁面。所以評估 JavaScript 的負擔時,不能只用自己的開發機去試,而要想到真正的使用者可能拿著一支運算吃緊的手機。主執行緒這條路一旦塞住,不管畫面多漂亮,使用者的每一次點擊都得排隊,體感就是慢。

1.3 一張表看清各類資源的影響

資源類型 主要影響 優化方向
阻塞渲染的 JS 延後首次繪製 加 defer 或 async、延後載入
大型 JS 套件 佔用主執行緒、拖慢互動 分割程式碼、移除未用的部分
阻塞渲染的 CSS 延後首次繪製 抽出首屏關鍵樣式內嵌
第三方腳本 不可控的額外負擔 延後或改用非同步載入

2. 把 JavaScript 減量、延後、切開

處理 JavaScript 有三個方向,一是減少總量,二是延後非必要的執行,三是把大檔案切成需要時才載入的小塊。這三招搭配起來,效果最實在。單用一招效果有限,合在一起才看得出明顯的改善。這三招的順序也有講究:我們通常先做減量,因為移除用不到的程式碼是沒有副作用的淨收益;再做延後,把非首屏必要的執行往後挪;最後才做切割,因為切割牽涉到打包方式的調整,工程成本最高。照這個順序走,團隊能先用低成本的動作把明顯的肥肉清掉,再決定要不要投入較大的工程去做細緻的切割。

2.1 用 defer 與 async 解除阻塞

大部分的腳本並不需要在解析 HTML 的當下就執行。加上 defer,腳本會在背景下載、等 HTML 解析完才依序執行,不會打斷繪製;async 則是下載完就執行,適合彼此獨立、不依賴順序的腳本,例如分析工具。光是把主要腳本從阻塞改成 defer,首次繪製往往就能提早不少。

兩者的差別值得多說一句:defer 會保留腳本之間的執行順序,等 HTML 解析完才依序跑,所以適合那些彼此有依賴、先後不能亂的主要腳本;async 則是誰先下載完誰先執行,順序不保證,適合那種跑早跑晚都不影響別人的獨立腳本。搞混的後果,是把有依賴關係的腳本標成 async,結果偶爾出現某個功能因為依賴還沒載入而報錯。拿不定主意時,主要腳本用 defer 是比較穩的預設選擇。

<script src="/js/app.js" defer></script>
<script src="/js/analytics.js" async></script>

2.2 移除用不到的程式碼

網站用久了,經常殘留早就停用的追蹤碼、被註解掉卻仍載入的舊套件、整包引入卻只用到一兩個功能的函式庫。這些都是白白增加的負擔。我們稽核時一定會抓這類殘留。曾有個網站還掛著早已停止運作的舊版分析代碼,雖然被註解,仍在增加請求與解析成本,清掉之後乾淨不少。定期檢視載入了哪些資源、哪些其實沒被用到,是很值得養成的習慣。我們通常會建議團隊在每次改版或季度檢視時,把瀏覽器開發者工具的網路面板打開,逐一看哪些檔案被載入、體積多大、實際用到多少,把那些以前需要、現在早就沒用的東西一併清掉。這種清理不會改變任何功能,卻能實實在在減輕每一次載入的負擔。

2.3 依需求切割與延後載入

不是每一段功能都要在頁面一開始就載入。像是彈窗、下方才出現的互動模組、非首屏的功能,可以等使用者真的需要、或捲動到附近時再載入。把首屏必要的程式碼跟其餘切開,讓一開始下載的量降到最低,首屏互動會明顯變輕快。這種切法的好處是,使用者要用到的功能一個都沒少,只是把它們出現的時機往後挪到真正需要的那一刻。

實務上最常見、也最划算的一個切割點,是首屏與首屏以下的分界。使用者打開頁面的那一瞬間,真正看得到、會立刻互動的就是首屏那一塊,下面的內容和功能都可以等他開始捲動再載入。我們會建議團隊先盤一次:哪些腳本是首屏一定要的,哪些其實是為了下方某個區塊而載入的,把後者延後,一開始要處理的量就少了一大截。這種調整不需要重寫功能,只是把載入的時機重新安排,對第一眼的互動體感改善卻很直接。

3. 讓 CSS 不再擋住第一眼畫面

CSS 雖然不像 JavaScript 那樣佔用執行緒,但它會阻擋首次繪製。使用者要等瀏覽器拿到樣式才看得到有樣子的畫面,所以 CSS 的載入策略同樣關鍵。處理得好,第一眼畫面可以快上一截。

3.1 抽出首屏關鍵樣式

一個有效的做法,是把首屏也就是使用者一開始就看得到的區域所需的關鍵樣式抽出來,直接內嵌在 HTML 的 <head> 裡,其餘完整樣式表則以非阻塞方式在後面載入。這樣瀏覽器拿到 HTML 幾乎就能立刻畫出有樣式的首屏,不必空等整份樣式表。使用者第一眼看到的,就是一個已經排好版的畫面,而不是一片沒有樣式的裸文字。

要提醒的是,抽關鍵樣式不是把整份樣式表複製進 HTML,那反而會讓 HTML 變得臃腫。關鍵的做法是只挑出首屏真正用到的那一小部分樣式,其餘完整樣式表仍以非阻塞方式在後面載入,等首屏畫好了再補上。這件事手動維護會比較費神,因為頁面改版後首屏用到的樣式也會變,所以多數團隊會靠工具自動抽取,並在改版時重新產生一次,避免內嵌的樣式和實際頁面對不上。做對了,使用者幾乎感覺不到白畫面的等待,頁面打開就是成形的。

3.2 清掉沒在用的樣式

許多網站直接引入整套前端框架的樣式表,但實際只用到其中一小部分。多出來的規則不只增加檔案大小,也拉長瀏覽器解析的時間。我們建議定期用工具找出頁面實際沒用到的 CSS,該精簡就精簡。我們的經驗是,網站經過幾輪改版後,樣式表裡往往堆積了對應到早已刪除區塊的舊規則,這些死掉的樣式沒人敢動、也沒人記得,就一直留著。趁改版時做一次盤點,把確定不再使用的樣式移除,長期下來樣式表不會無止盡膨脹,載入也更輕快。維持樣式表精瘦,對每一次載入都有幫助。

3.3 減少字型與外部樣式的往返

除了自家的樣式表,網頁字型與外部載入的樣式檔也常是被忽略的阻礙。載入太多種字重、字型檔案又大,會延後文字正確顯示的時間;從外部網域拉樣式,則多了一次連線與往返的成本。我們會建議只保留真正用到的字重、盡量使用效率較好的字型格式,並讓文字在字型載入期間先以系統字顯示,避免使用者盯著一片空白等字出現。這些細節單獨看不起眼,累積起來對首屏的呈現速度影響不小。把外部依賴降到最低,頁面對自己能掌控的部分就越多。

4. 壓低 TTFB,並看它如何反映在體驗指標

前面談的是資源本身,這一段回到更前面的環節,伺服器要多久才吐出第一個位元組,也就是 TTFB。這個數字若一開始就慢,後面再怎麼優化,起跑點都輸人一截。它是整條載入鏈的第一棒,慢了,後面全部跟著遞延。

4.1 TTFB 慢通常卡在哪

伺服器回應慢,常見原因包括後端運算太重、資料庫查詢沒有最佳化、每次請求都即時運算而沒有快取、主機所在位置離使用者太遠。我們看過一些網站每一次請求都重新跑完整套邏輯生成頁面,明明內容不常變動,卻不做任何快取,TTFB 自然居高不下。找出是哪一類原因,才知道該從運算、資料庫、快取還是主機位置著手。把病因分清楚,比盲目換主機或加硬體有效得多。

一個實用的判斷方法,是先把 TTFB 拆成「伺服器處理時間」和「網路傳輸時間」兩段來看。如果是處理時間長,問題多半在後端運算或資料庫;如果是傳輸時間長,問題則偏向主機位置或連線品質。我們遇過團隊一看到 TTFB 高就急著升級主機規格,結果真正的瓶頸是一支沒建索引的資料庫查詢,換了更貴的主機也沒改善多少。先量清楚時間花在哪一段,才不會把錢和力氣砸在錯的地方。

4.2 快取與內容傳遞是兩帖良藥

對不常變動的頁面,導入伺服器端快取或改用預先產生好靜態頁的方式,能讓伺服器省下重複運算,回應快很多。搭配內容傳遞網路把靜態資源放到離使用者較近的節點,也能縮短傳輸距離。這兩件事處理好,TTFB 通常有相當程度的改善。

快取要注意的是適用範圍。像文章、產品介紹這種不常變動的頁面,很適合快取或預先產生靜態頁;但像購物車、會員中心這類每個人看到的內容都不同的頁面,就不能一律套用同一份快取,否則會把 A 使用者的資料顯示給 B。我們在導入快取時,會先把頁面分成「大家看到的都一樣」和「因人而異」兩類,前者盡量快取,後者則針對其中不變的部分局部處理。分清楚這條界線,才能在加快速度的同時不出現資料錯置的意外。下表整理常見成因與對應手段:

TTFB 偏慢的成因 對應做法
每次請求即時運算 導入伺服器端快取或預先產生靜態頁
資料庫查詢緩慢 優化查詢、建立適當索引
主機距使用者遠 透過內容傳遞網路就近提供資源
後端邏輯過重 精簡流程、非必要運算移到背景

4.3 這些調整最後對應到哪些指標

把上面幾件事做好,會直接反映在核心體驗指標上。抽出關鍵 CSS、把主要腳本改成延後執行,能讓最大內容元素更早出現,也就是最大內容繪製時間變短。減少主執行緒被大包 JavaScript 佔用,使用者互動的回應會更即時,互動延遲指標跟著改善。而穩定的版面配置、不因資源晚到而位移,則對應到版面位移的分數。TTFB 壓低則像是替所有後續指標爭取到更早的起跑點。這些數字背後,其實就是使用者「打開就看得到、想按就按得動」的真實體感,這也是我們一直認為速度優化最值得投入的原因。

我們會建議不要只盯著單一指標的分數起伏,而是理解每項優化動到的是哪一個環節。抽關鍵 CSS 和延後腳本改的是「畫面多快出現」,減少主執行緒佔用改的是「出現後多快能用」,壓低 TTFB 改的是「整條鏈的起跑時間」,穩定版面改的是「出現過程會不會亂跳」。把每個動作對應到它該改善的指標,你就不會做了一堆優化卻搞不清楚哪個在生效、哪個白做。速度優化最怕的就是憑感覺亂調,調完分數動了也說不出為什麼。先建立這層對應關係,後面每一次改動都能驗證它有沒有打中預期的環節,這比追求某個好看的總分務實得多。

5. 常見問題

5.1 defer 和 async 到底該用哪一個

如果腳本之間有執行順序的依賴、或需要等 HTML 解析完再跑,用 defer;如果腳本完全獨立、誰先誰後都沒差,例如第三方分析工具,用 async。多數網站的主要腳本用 defer 會比較安全,因為它保留了執行順序,又不會阻擋繪製。

5.2 把所有 CSS 都內嵌進 HTML 是不是更快

不建議全部內嵌。只把首屏關鍵樣式內嵌就好,其餘樣式表非阻塞載入。若把整份龐大的樣式表都塞進 HTML,會讓 HTML 本身變得笨重,反而拖慢首個位元組與解析。內嵌的用意是讓首屏快點出現,不是把所有東西都擠到前面。

5.3 TTFB 多少才算好

沒有適用所有情況的單一數字,會受主機、地區、頁面性質影響。與其追某個絕對值,我們更建議跟自己過去的表現與同類頁面比較,看調整快取與後端之後是否穩定下降。方向對了,比死守一個門檻實際。趨勢往下走,通常就代表你動對了地方。

5.4 用了很多第三方腳本,速度就一定救不回來嗎

不會完全沒救。可以把第三方腳本改成非同步或延後載入,評估哪些是真的必要、哪些可以移除,並留意它們對主執行緒的佔用。減量加上延後,通常能把影響壓到可接受的範圍。第三方腳本的難處在於你無法改它的內容,但你完全可以決定它何時載入、載不載入。

5.5 減少 JavaScript 會不會影響網站功能

只要做法正確就不會。重點是移除確實沒被使用的程式碼、延後非首屏必要的功能,而不是砍掉正在用的邏輯。切割與延後載入的目的是讓功能在需要時才出現,體驗反而更順。會出問題的通常是沒搞清楚哪些在用就亂刪,而不是優化本身。

5.6 這些優化做完,排名會馬上上升嗎

速度是眾多排名考量之一,改善體驗指標有幫助,但排名還受內容品質、連結、競爭程度等因素影響。我們會把速度優化看成改善使用者體驗與降低跳出的基本功,成效通常是逐步累積,而非一夜見效。把它當成長期該維持的底盤,而不是一次性的衝刺,心態會比較健康。實務上我們看到速度帶來的回報,更多是在「不拖後腿」這件事上:一個快的頁面不保證你贏,但一個慢的頁面會在使用者還沒讀到內容前就先流失一批人,也讓其他優化的效果打折。與其期待它單獨把排名往上推,不如把它當成讓內容與其他努力能夠完整發揮的前提。根據我們的服務經驗,速度改善後最先看到的,往往是跳出率下降與頁面停留變長,排名的變化則是在這些體驗訊號累積一段時間後才慢慢反映出來。