本篇重點
- 使用 jsDelivr CDN 串接 GitHub Pages 資源,延長快取時長、加速載入
- 了解 GitHub Pages 與 jsDelivr 在快取、全球節點、資源更新速度上的差異
- 了解 jsDelivr CDN 串接原理與版本控制
- 使用 Hexo 過濾器實現本地資源重寫
- 使用自動化佈署腳本與 CDN Tag 機制實現佈署後強制更新快取
jsDelivr CDN
一個免費、開源的公共 CDN,專門用於託管 GitHub 倉庫、npm 包等資源,並提供全球多個節點 + 彈性快取 + fallback 機制。
CDN(內容傳遞網路,Content Delivery Network)是一種分散式伺服器網路系統,其核心目的是加速網站內容的傳輸速度,讓全球各地的使用者都能快速存取網站內容。
串接 GitHub Pages 的運作方式
支援透過特定的 URL 結構來直接存取 GitHub 倉庫中的文件。
URL 結構:
jsdelivr cdn1 2 3 4 5 6 7 8 9
| https://cdn.jsdelivr.net/gh/使用者名稱/倉庫名稱@版本號/文件路徑
// 範例 https://cdn.jsdelivr.net/gh/forgetfulengineer/forgetfulengineer.github.io@v-20251208-172935/gallery/covers/Handling-Case-Sensitivity-Issues-in-GitHub-Deployment.webp
https://cdn.jsdelivr.net/gh/forgetfulengineer/forgetfulengineer.github.io@lateset/gallery/covers/Handling-Case-Sensitivity-Issues-in-GitHub-Deployment.webp
// 無使用版本號,會自動存取主分支的資源,例如:@main https://cdn.jsdelivr.net/gh/forgetfulengineer/forgetfulengineer.github.io/gallery/covers/Handling-Case-Sensitivity-Issues-in-GitHub-Deployment.webp
|
@版本號 參數可為 Git 分支名、Commit SHA 或 Tag 名稱。此參數用於鎖定特定版本的資源,是實現 CDN 快取強制更新(Cache Busting)的核心機制。
特性:
- 從 GitHub API / 原始檔案擷取資料。
- 調用的是 GitHub 資源,但由 jsDelivr 分發,避免 GitHub 本機延遲。
- 若使用 tag 或 commit hash,jsDelivr 視為不可變版本,會使用長效快取(例如
max-age 1 年)。 - 若使用分支(如
main、latest),屬於「可變內容」,會使用短效快取(例如 max-age 600 秒)。 - 整合多家 CDN,若某一節點故障,自動 fallback 至其他節點,提升可用性與抗故障能力。
- 適合用於靜態資源 (CSS, JS, 圖片),利用 Multi-CDN 優勢最大化下載速度。
GitHub Pages 資源 vs jsDelivr CDN
GitHub Pages 本身透過 Fastly 作為 CDN 層提供全球分發與快取,因此具備一定的延遲優化與邊緣緩存。jsDelivr CDN 的優勢在於提供「版本鎖定 + 公共 CDN」、「多供應商 fallback」、「immutable 快取」等特性,並且台灣用戶因 jsDelivr 結合 Cloudflare、Bunny 節點能增加命中率與載入速度。
Hexo 實作方式

目標是透過 Hexo 過濾器(Filter)在網站生成時,將 HTML 文件中指向本地靜態資源的相對路徑替換為 jsdelivr CDN 的絕對路徑。
過濾器重寫資源 URL
新增腳本(rewrite_local_asset.js,以下稱 ‘重寫腳本’)放在 scripts 資料夾下,讓 Hexo 初始化時自動執行。
rewrite_local_asset.js >folded1 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 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71
| /** * 本地資源重寫腳本 * 使用 Cheerio 解析 HTML,將 / 開頭的路徑改寫為 jsdelivr CDN prefix */
'use strict';
const cheerio = require('cheerio');
const enable = true; let prefix = 'https://cdn.jsdelivr.net/gh/使用者名稱/倉庫名稱';
hexo.extend.filter.register('after_render:html', function (html) { // 本地開發模式不重寫 if (['s', 'server'].includes(hexo.env.cmd)) return html;
if (!enable || !prefix) return html;
prefix = prefix.replace(/\/+$/, '');
if (process.env.CDN_TAG) { prefix = prefix.includes('@') ? prefix.replace(/@([^\/]+)/, `@${process.env.CDN_TAG}`) : `${prefix}@${process.env.CDN_TAG}`; }
if (!prefix) return html;
const $ = cheerio.load(html, { decodeEntities: false });
/** * 通用重寫與 Fallback 邏輯 * @param {object} el - Cheerio 元素物件 * @param {string} attrName - 屬性名稱 (src 或 href) */ const rewriteAndSetFallback = (el, attrName) => { const $el = $(el); const originalUrl = $el.attr(attrName);
// 如果 URL 為空或不是以 / 開頭的 URL均不處理 if (!originalUrl || !originalUrl.startsWith('/')) return;
// 將本地路徑轉換為 CDN 路徑 const cdnUrl = prefix + originalUrl;
// Fallback 設置 // 當 CDN 載入失敗時(onerror handler),將 src/href 切換回本地資源 $el.attr('onerror', `this.onerror=null;this.setAttribute('${attrName}', '${originalUrl}');`); // 設置重寫後的 CDN URL $el.attr(attrName, cdnUrl); };
$('img').each((i, el) => rewriteAndSetFallback(el, 'src')); $('script').each((i, el) => rewriteAndSetFallback(el, 'src')); $('link').each((i, el) => { const $el = $(el); const rel = $el.attr('rel');
// 排除 Favicon、Icons、Sitemap、Alternate 等特定 rel 屬性 if (rel && /icon|apple-touch-icon|sitemap|manifest|alternate/i.test(rel)) { return; }
rewriteAndSetFallback(el, 'href'); });
return $.html(); });
|
說明
重寫腳本註冊 after_render:html 過濾器,在 Hexo 將 Markdown 轉換為 HTML 之後執行。
重寫腳本解析生成的 HTML(使用 Cheerio),針對所有以 / 開頭 (root-relative) 的資源 (例如 <link href="/css/style.css">) 進行 rewrite。
- 注入 CDN Tag: 腳本首先檢查環境變數
process.env.CDN_TAG。如果存在,它會將這個 Tag 名稱(例如 v-20251208-140000)注入到 CDN 的 URL Prefix 中,實現版本控制。 - CDN URL 生成: 對於符合條件的本地路徑
originalUrl,新的 CDN URL cdnUrl 會被構建為 prefix + originalUrl。
範例:
如果 prefix 為 https://cdn.jsdelivr.net/gh/user/repo@v-latest,originalUrl 為 /images/logo.png,那 cdnUrl 會是 https://cdn.jsdelivr.net/gh/user/repo@v-latest/images/logo.png。
避免 CDN 故障 (cdn 回傳 404 或 downtime),對所有被 rewrite 的元素加上 onerror fallback:
1
| $el.attr('onerror', `this.onerror=null;this.setAttribute('${attrName}', '${originalUrl}');`);
|
this.onerror=null 防止在回退到原始路徑後若仍失敗導致無限循環。- 將元素的
src 或 href 屬性切換回原始的本地相對路徑 (originalUrl),確保網站的可用性。
排除特定 rel 屬性的 <link> 元素是為了減少 SEO、快取策略以及跨來源安全限制(CORS)的問題。
自動化佈署腳本與 CDN 版本控制
新增腳本(deploy.sh,以下稱 ‘佈署腳本’)放在 Hexo 專案資料夾下,負責網站的清理、生成、佈署,以及最重要的 CDN 快取強制更新機制。
rewrite_local_asset.js >folded1 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 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78
|
REPO_URL="https://github.com/使用者名稱/倉庫名稱" USER_NAME="使用者名稱" USER_EMAIL="使用者信箱" FIRST_DEPLOY=0
CDN_TAG="v-$(date +'%Y%m%d-%H%M%S')"
echo -e "\n檢查 .deploy_git 資料夾是否存在..." if [ -d ".deploy_git" ]; then cd .deploy_git || { echo -e "\n切換到 .deploy_git 資料夾失敗"; exit 1; } echo -e "\n拉取 github page 最新進度..." git pull || { echo -e "\n拉取進度失敗"; exit 1; } cd .. || { echo -e "\n返回 Hexo 資料夾失敗"; exit 1; } else echo -e "\n檢查 GitHub Pages repository 是否已有 commit..." if git ls-remote --exit-code "$REPO_URL" HEAD > /dev/null 2>&1; then echo -e "\nClone repository 到 .deploy_git 資料夾..." git clone "$REPO_URL" .deploy_git || { echo -e "\nClone repository 失敗"; exit 1; }
cd .deploy_git || { echo -e "\n切換到 .deploy_git 資料夾失敗"; exit 1; } git config --local user.name "$USER_NAME" git config --local user.email "$USER_EMAIL" git config --local core.ignorecase false cd .. || { echo -e "\n返回 Hexo 資料夾失敗"; exit 1; } else echo -e "\nGitHub Pages repository 尚無 commit,進行首次佈署" FIRST_DEPLOY=1 fi fi
echo -e "\n清理舊的生成文件..." hexo clean || { echo -e "\nHexo 清理失敗"; exit 1; }
export CDN_TAG="$CDN_TAG"
echo -e "\n生成新的靜態文件並佈署網站..." hexo g || { echo -e "\nHexo 生成失敗"; exit 1; } hexo d || { echo -e "\nHexo 佈署失敗"; exit 1; }
echo -e "\n正在對 GitHub Pages 建立 Tag: ${CDN_TAG}..."
cd .deploy_git || { echo -e "\n無法進入 .deploy_git,Tag 建立失敗"; exit 1; }
LATEST_COMMIT=$(git rev-parse HEAD) echo -e "\n最新 Commit: ${LATEST_COMMIT}"
git tag -a "$CDN_TAG" $LATEST_COMMIT -m "Release: $LATEST_COMMIT"
echo -e "\n推送 Tag 到 GitHub..." git push origin "$CDN_TAG" cd ..
if [ "$FIRST_DEPLOY" -eq 1 ]; then echo -e "\n首次佈署,設置 git config --local core.ignorecase false" cd .deploy_git || { echo -e "\n切換到 .deploy_git 資料夾失敗"; exit 1; } git config --local core.ignorecase false cd .. || { echo -e "\n返回 Hexo 資料夾失敗"; exit 1; } fi
echo -e "\n佈署完成"
|
說明
- 產生唯一的
CDN_TAG:腳本在佈署開始時,自動生成唯一且代表當前版本的 CDN_TAG,格式為 v-YYYYMMDD-HHMMSS。 - 注入環境變數:
export CDN_TAG="$CDN_TAG" 將 Tag 注入到環境變數中,供重寫腳本讀取並整合進 CDN URL。 - 將該
CDN_TAG 設為環境變數,供重寫腳本使用,以產生對應版本的 CDN URL。 - 生成並佈署網站:
hexo g (Generate) 執行時,重寫腳本會使用最新的 CDN_TAG 重寫所有本地資源路徑。 - 建立並推送 Git Tag:在 Hexo 成功佈署,將靜態文件推送到 GitHub Pages 倉庫後,腳本在
.deploy_git 資料夾內,對最新的 Commit 建立並推送該 CDN_TAG 到遠端 GitHub 倉庫。 - CDN URL 隨版本改變(tag 不同),實現「Cache Busting」策略:舊版資源仍可被 cache,但新版資源 URL 不同,使用者會重新拉取最新資源,實現強制更新,避免被舊資源覆蓋或快取污染。
只說明 deploy.sh Tag 邏輯與 CDN 快取更新的部分,其他流程說明可以到【Hexo】多台電腦佈署 GitHub page 的問題。 查看
結論
雖然 GitHub Pages 本身已透過 Fastly 提供優秀的 CDN 服務,但透過 jsdelivr 的 Multi-CDN 架構與 Hexo 自動化腳本的整合,可以進一步優化靜態資源的交付策略。
此方案的核心價值在於「資源版本控制」:利用自動化的 Git Tag 機制,確保每次佈署的資源都擁有唯一的 URL,解決了靜態資源的快取更新問題,更讓網站能享受到 jsdelivr 針對資源提供的長效快取,同時配合 onerror 回退機制確保了服務的高可用性。
延伸閱讀