網站速度優化 四種關鍵方法 完整指南:掌握延遲載入、Code Splitting 與打包檔分析,打造極速網頁體驗

me
林彥成
2021-03-09 | 5 min.
文章目錄
  1. 1. 如何顯著加快網站開啟速度?
  2. 2. 1. 延遲載入資源 (Lazy Load)
    1. 2.1. Script 載入方式 (async, defer, lazyload)
  3. 3. 三種方法 (async, defer, lazyload)
    1. 3.1. 使用程式懶載入
  4. 4. 2. 程式拆分 (Code Splitting)
  5. 5. 3. 打包檔分析器 (Bundle Analyzer)
  6. 6. 4. 資源優化 (Resource Optimization)
  7. 7. 網站速度測試工具
  8. 8. FAQ:網站速度優化常見問題
    1. 8.1. Q1:async 與 defer 我該怎麼選?
    2. 8.2. Q2:實作 Lazy Load 會影響 SEO 嗎?
    3. 8.3. Q3:為什麼 HTTP/2 之後「打包檔案」依然重要?

如何顯著加快網站開啟速度?

提升網站開啟速度(載入效能)的核心在於「減少請求體積」與「延後非必要載入」。主要方法包含:1. 延遲載入 (Lazy Load):透過 IntersectionObserver 僅在進入視窗時載入圖片,並善用 <script>asyncdefer 屬性優化腳本執行;2. 程式碼拆分 (Code Splitting):利用 Webpack 將模組按路由拆分,避免首次進入時下載整站腳本;3. 打包檔分析 (Bundle Analyzer):識別大型套件(如 moment.js)並尋找輕量替代方案(如 day.js);4. 資源優化與快取:透過 Service Worker 實作 PWA 離線快取,並對靜態圖片進行無損壓縮。這些手段能有效縮短首屏載入時間,顯著提升 Pagespeed 分數與用戶留存率。


這篇文章會簡介加快網站開啟速度的四種常見方法,透過延遲載入資源、程式拆分、打包檔分析、資源優化幾個項目來作優化,並且用網站速度測試工具來進行檢核是否有效。

網站在載入效能上會遇到哪些問題?

  1. 靜態資源過多載入過慢。
  2. 腳本複雜執行時間過長。
  3. RWD 的設計造成樣式檔越來越大包。

1. 延遲載入資源 (Lazy Load)

圖片的部份 npm 上有個套件叫 lazyload,用的招式其實就是修改了 img 圖片來源的 attribute,並透過觀察的方式把 attribute 換回來進行 lazyload。

<img src="<image-url>" width=400 height=400>
<img class="lazyload" data-src="<image-url>" width=400 height=400>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
// default settings
const defaults = {
src: "data-src",
srcset: "data-srcset",
selector: ".lazyload",
root: null,
rootMargin: "0px",
threshold: 0
};
this.images = document.querySelectorAll(this.settings.selector);
---

let observerConfig = {
root: this.settings.root,
rootMargin: this.settings.rootMargin,
threshold: [this.settings.threshold]
};

this.observer = new IntersectionObserver(function(entries) {
Array.prototype.forEach.call(entries, function (entry) {
if (entry.isIntersecting) {
self.observer.unobserve(entry.target);
let src = entry.target.getAttribute(self.settings.src);
let srcset = entry.target.getAttribute(self.settings.srcset);
if ("img" === entry.target.tagName.toLowerCase()) {
if (src) {
entry.target.src = src;
}
if (srcset) {
entry.target.srcset = srcset;
}
} else {
entry.target.style.backgroundImage = "url(" + src + ")";
}
}
});
}, observerConfig);

Array.prototype.forEach.call(this.images, function (image) {
self.observer.observe(image);
});

Script 載入方式 (async, defer, lazyload)

HTML5 <script> tag 中有多 async 和 defer 這兩個 attribute 可以使用,透過這兩個 attribute 我們可以非同步的去載入腳本,來加快網頁載入的速度。

三種方法 (async, defer, lazyload)

  • async: 如果可以就非同步,不保證按照順序,若沒有相依性且拿來操作畫面,是最佳做法
  • defer: 頁面載入後都執行完才按照順序執行,可用來加速網頁載入
  • lazyload 用程式控制載入時機

async 按照 MDN 文件 說明在 inline 的狀況不會有效果,必須帶有 src 屬性。:

1
2
3
<script async>
var code;
</script>
1
2
3
var script = document.createElement("script");
script.innerHTML = "code";
document.body.appendChild(script);

使用程式懶載入

透過程式來 lazyload 腳本,不想用新潮寫法的話透過以下簡單寫法就可以延後載入非必要的腳本,譬如在我部落格中的 diqus 就是透過這樣的方法進行 lazyload 的,我會偵測是否有捲動到某個特殊的 div 如果有才進行載入。

1
2
3
4
5
6
7
8
9
10
11
12
function loadScript(src, callback) {
let script = document.createElement("script");
script.src = src;
script.onload = () => callback(script);
document.head.append(script);
}

loadScript("/script.js", function (script) {
loadScript("/script2.js", function (script) {
loadScript("/script3.js", function (script) {});
});
});

Promise 的範例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
function newScript() {
return new Promise((resolve, reject) => {
const script = document.createElement("script");
script.src = "./test.min.js";
script.addEventListener("load", () => {
resolve();
});
script.addEventListener("error", (e) => {
reject(e);
});
document.body.appendChild(script);
});
}
newScript()
.then(() => {
self.setState({ status: true });
})
.catch(() => {
self.setState({ status: false });
});

Google Tag Manager 的範例

1
2
3
4
5
6
7
8
9
10
11
12
13
(function (a, b, c, d, e) {
a[d] = a[d] || [];
a[d].push({
"gtm.start": new Date().getTime(),
event: "gtm.js",
});
var f = b.getElementsByTagName(c)[0],
g = b.createElement(c),
h = "dataLayer" != d ? "&l=" + d : "";
g.async = true;
g.src = "https://www.googletagmanager.com/gtm.js?id=" + e + h;
f.parentNode.insertBefore(g, f);
})(window, document, "script", "dataLayer", "GTM-XXXXXXX");

2. 程式拆分 (Code Splitting)

JavaScript 在早期伺服器渲染的階段,檔案較小且大多只有擔任簡單的事件互動,可以說幾乎沒有優化的需求,近幾年 Node.js 的快速發展加上 SPA 的概念出現後,才開始慢慢出現比較大且複雜的腳本。

當腳本複雜化後就開始有了 module 的概念,方便在撰寫的時候重複組合使用,在 JavaScript 中的 module 有很多種載入方式,像是早期的 CommonJS 或 AMD-based 的 RequireJS 或是都支援的 UMD,再來就是用 Webpack 搭配 Babel 直接用 ES6 的寫法。

Webpack 這類的工具起初的目標是幫我們把拆分的 module 做好打包,方便我們用 bundle 後的檔案進行發佈,當腳本開始變多時就會有越來越多當下不需要但又會被載入的部分,隨後應運而生的就是 code splitting 的概念。

Code splitting 如何去優化網站的載入效能?
因為腳本執行與樣式渲染都會影響網站的載入,原則上就是透過是當的分類拆分,透過 lazyload 的概念來達到優化的目的。Code splitting 能夠做到的應用場景如下:

  1. 按照路由切分在特定頁面只載入相關的腳本與樣式檔,譬如在個人資料頁面就不載入商品或是行銷活動頁面相關程式碼。
  2. 常用的第三方套件被放在某個 common.chunk.js 中,這樣瀏覽器就能夠進行快取,這樣即使要看到新版的應用,也不一定要重新下載這個部分。
  3. CSS-in-JS 的方案讓樣式檔可以跟著元件一起載入,或是盡量使用原子化設計的撰寫方式,並且把相關的樣式都放在 common.css 中讓瀏覽器進行快取。

會有 code splitting 另一方面也是 HTTP2 出現之後的影響,針對同一個網域可以平行處理資源,但值得注意的是經網路上的大大測試同時很多小檔也不一定比一個大檔案快,所以其實是要嘗試出一個中間值。

3. 打包檔分析器 (Bundle Analyzer)

在使用的套件越來越多的情況底下,其實延遲載入、程式拆分的做法改善也有限,有時候其實很難發覺到底是哪個套件對專案影響最大,這時候就可以使用打包檔分析器來找看看兇手是誰,解決的方式也許是看看

  1. import 的時候使用 partial import 的方式而不是直接全部引入 (import has from 'lodash/has';
    vs import { has } from 'lodash';),這個部分跟 code splitting 其實是類似的概念
  2. 找替代方案,像是 moment.js 就屬於大包的套件,就有 day.js 或是 date-fns 這類相對小包的套件可作替代

範例頁面可以看這裡:
https://cdn.rawgit.com/danvk/source-map-explorer/08b0e130cb9345f9061760bf8a8d9136ea60b457/demo.html

4. 資源優化 (Resource Optimization)

  1. 透過撰寫 serviceWorker 來實作 PWA 靜態資源快取
1
2
3
4
5
6
7
8
9
10
11
12
13
self.addEventListener("install", function (event) {
event.waitUntil(
caches.open(cacheName).then(function (cache) {
return cache.addAll([
"/css/bootstrap.css",
"/css/main.css",
"/js/bootstrap.min.js",
"/js/jquery.min.js",
"/offline.html",
]);
})
);
});
  1. 資料面的快取
  1. 靜態圖片資源進行無損壓縮
  1. Responsive Images

網站速度測試工具

Google 提供了底下兩套免費工具:

Google 提供了底下兩套免費的工具 PageSpeed Insights 較適合非開發者使用會是用真實環境去測試,Chrome Lighthouse 則比較適合前端工程師本地端測試使用。

PageSpeed Insights
PageSpeed Insights

Chrome Lighthouse
Chrome Lighthouse


FAQ:網站速度優化常見問題

Q1:asyncdefer 我該怎麼選?

A:如果您的腳本不依賴其他程式碼(如 Google Analytics),選 async。如果腳本需要與 DOM 互動,或者有順序相依性(如 React 應用的主入口),選 defer 是最保險的做法。

Q2:實作 Lazy Load 會影響 SEO 嗎?

A:早期的爬蟲可能抓不到 data-src 的內容。但現在 Googlebot 已經能很好地處理 JavaScript。建議實作時搭配 <noscript> 標籤,或者確保重要圖片依然透過標準 src 載入,以達到最佳平衡。

Q3:為什麼 HTTP/2 之後「打包檔案」依然重要?

A:雖然 HTTP/2 解決了連線阻塞,但過多的小檔案依然會增加壓縮效率的損耗(如重複的 Header 資訊)。此外,解析數百個小腳本的 CPU 開銷有時比解析一個大腳本更高。目前業界的共識是將程式碼拆分為約 50KB~100KB 的幾個主 Block。



喜歡這篇文章,請幫忙拍拍手喔 🤣