SEO 完整攻略

探索 SEO 的最佳實踐,提升網站能見度與流量。

頁面速度優化

快速載入不只是好用,也能讓搜尋引擎更愛你。

頁面速度優化

資源合併與壓縮:讓網頁載入更順暢的祕技

把 CSS 與 JS 合併成一個檔案,減少 HTTP 請求

把 CSS 與 JS 合併成一個檔案,減少 HTTP 請求

在現今的網頁開發中,HTTP 請求的數量直接影響使用者看到內容前的等待時間。將多個 CSS 與 JS 檔案合併成單一檔,可減少請求次數,從而提升整體載入速度。

以下以實際範例說明如何完成這一步驟,並提供常見工具的使用方法。

合併檔案帶來的好處

  • 減少網路往返:每一次 HTTP 請求都需要 DNS 查詢、TCP 三次握手及可能的 TLS 握手,合併後可以省去這些額外開銷。
  • 降低瀏覽器限制:大多數瀏覽器對同一來源單一連線有請求上限,合併可避免因超過限制而造成的阻塞。
  • 簡化部署與維護:只需管理一個檔案,即可一次性更新所有樣式與行為。

步驟說明

  1. 收集原始檔:先確定網站目前使用的 CSS 與 JS 檔案路徑與順序,尤其注意依賴關係(如 jQuery 必須在其他插件前載入)。

  2. 建立合併檔:將所有 CSS 放進一個 .css 檔、所有 JS 放進一個 .js 檔。若兩者必須同時使用,可考慮合併為單一 .js 檔,並在其中嵌入 <style> 標籤載入 CSS。

  3. 更新 HTML:將原先多個 <link><script> 改寫成對應的單一引用。例如

<link rel="stylesheet" href="assets/combined.css">
<script src="assets/combined.js"></script>
  1. 測試功能:確認所有互動與樣式均正常。若有遲延執行的腳本,可能需要使用 deferasync 屬性。

  2. 監控載入效能:可利用瀏覽器開發者工具或第三方服務(如 Lighthouse)驗證實際減少的請求數量與提升時間。

範例:合併 CSS 與 JS 為單一檔案

以下示範如何將兩個簡易樣式表及兩段腳本合併為 combined.js,並在其中插入 <style> 標籤載入 CSS。

// combined.js
/* ====================== CSS 部分 ===================== */
const style = document.createElement('style');
style.textContent = `
  body { font-family: Arial, sans-serif; background:#f9f9f9; }
  .btn { padding:10px 20px; background:#007bff; color:white; border:none; cursor:pointer; }
`;
document.head.appendChild(style);

/* ====================== JS 部分 ===================== */
function greet() {
  alert('歡迎來到合併後的頁面!');
}

document.addEventListener('DOMContentLoaded', () => {
  const btn = document.createElement('button');
  btn.className = 'btn';
  btn.textContent = '點我打招呼';
  btn.onclick = greet;
  document.body.appendChild(btn);
});

提示:若樣式與腳本量很大,可考慮先合併 CSS,再使用壓縮工具(如 UglifyJS)對 JS 做 minify,最後再執行上述步驟。

常用自動化工具範例

Gulp
const gulp = require('gulp');
const concat = require('gulp-concat');
const uglify = require('gulp-uglify');
const cleanCSS = require('gulp-clean-css');

// 合併並壓縮 CSS
function styles() {
  return gulp.src(['src/css/*.css'])
    .pipe(concat('combined.css'))
    .pipe(cleanCSS())
    .pipe(gulp.dest('dist')); 
}

// 合併並壓縮 JS
function scripts() {
  return gulp.src(['src/js/*.js'])
    .pipe(concat('combined.js'))
    .pipe(uglify())
    .pipe(gulp.dest('dist'));
}

exports.default = gulp.series(styles, scripts);
Webpack
const path = require('path');
module.exports = {
  mode: 'production',
  entry: {
    main: ['./src/css/style1.css', './src/css/style2.css', './src/js/script1.js', './src/js/script2.js']
  },
  output: {
    filename: 'combined.js',
    path: path.resolve(__dirname, 'dist')
  },
  module: {
    rules: [
      { test: /\.css$/i, use: ['style-loader', 'css-loader'] }
    ]
  }
};

小提醒:Webpack 的 mode: 'production' 已自動啟用 CSS 與 JS 的壓縮,無需額外設定。

使用工具壓縮 CSS、JS 內容:減小檔案大小

使用工具壓縮 CSS、JS 內容:減小檔案大小

網頁載入速度跟使用者體驗息息相關,最常見的瓶頸之一就是 CSSJavaScript 檔案太大。只要把這兩種資源壓縮(minify)或合併,就能顯著提升下載與執行時間。

步驟概覽
  • 確認檔案大小與載入次數:先用瀏覽器開發者工具的 Network 面板,找出哪些 CSS/JS 檔案最佔資源。
  • 選擇合適的壓縮工具:市面上有許多免費或付費的線上、CLI 與 IDE 插件可供挑選。
  • 自動化流程:把壓縮作業加入 build pipeline(例如 Gulp、Webpack 或 Vite),避免手動操作造成錯誤。
  • 測試與驗證:使用 Lighthouse、WebPageTest 等工具確認速度提升,並確保功能未受影響。
常見的 CSS/JS 壓縮工具清單
類別 工具名稱 使用方式 特色說明 範例指令或設定
線上 Minify Code 在瀏覽器輸入/貼上原始碼,按下壓縮即可 免安裝、即時查看結果 不需要命令列,只要點擊 "Compress" 即可
CLI UglifyJS (JavaScript) uglifyjs input.js -o output.min.js --compress --mangle 支援多種壓縮選項,適合 Node 專案 需要先安裝 npm install uglify-js -g
CLI CleanCSS (CSS) cleancss -o style.min.css style.css 自動清除註解、空白與重複規則 需安裝 npm i clean-css-cli -g
Build 系統 Webpack 在 webpack.config.js 設定 mode: 'production' 或使用 MiniCssExtractPlugin + css-minimizer-webpack-plugin 一站式打包、壓縮與模組化 請參考官方文件 https://webpack.js.org/guides/optimization/
Build 系統 Vite 內建 ESBuild,啟用 build.minify: true 開發時即支援 HMR,生產環境自動壓縮 在 vite.config.ts 設定 minify: 'terser' 或預設 true
實作範例:使用 Gulp 進行 CSS/JS 壓縮
  • 安裝必要套件
# 建立專案並安裝 gulp 與相關插件
npm init -y
gulpfile.js # 後面會建立此檔案
npm i gulp gulp-clean-css gulp-uglify gulp-concat --save-dev
  • 撰寫 gulpfile.js
const { src, dest, series } = require('gulp');
const cleanCSS = require('gulp-clean-css');
const uglify = require('gulp-uglify');
const concat = require('gulp-concat');

// CSS 壓縮
function css() {
  return src(['src/css/*.css'])
    .pipe(concat('style.min.css'))
    .pipe(cleanCSS({ compatibility: 'ie8' }))
    .pipe(dest('dist/css'));
}

// JS 壓縮
function js() {
  return src(['src/js/*.js'])
    .pipe(concat('bundle.min.js'))
    .pipe(uglify())
    .pipe(dest('dist/js'));
}

exports.default = series(css, js);
  • 執行 Gulp
gulp

之後 dist/css/style.min.cssdist/js/bundle.min.js 就完成了壓縮與合併。

小技巧:避免過度壓縮導致的錯誤
  • 保留必要的註解:有些框架或第三方腳本會依賴特定註解(例如 /* eslint-disable */),使用 --comments=some-pattern 參數可以保留。
  • 測試所有瀏覽器:某些舊版瀏覽器對於壓縮後的變數名或語法可能不相容,建議在 Chrome、Edge、Safari 與 Firefox 上做快速驗證。
  • 版本控制備份:把原始碼放進 Git,並以 git add -p 方式只提交需要改動的檔案,避免一次性修改大量檔造成不可逆轉的錯誤。

剃除未用到的程式碼,讓資源更精簡

剃除未用到的程式碼,讓資源更精簡

在開發網頁時,我們往往會不經意地把許多不需要的 CSS、JS 或圖片留在專案裡。這些「冗餘」程式碼就像是背包裡的舊衣服,雖然佔空間卻根本不用,還會拖慢載入速度。

比如說:

  • 你使用了 Bootstrap,但只用了其中的按鈕樣式;整個框架的其他 CSS 還在檔案裡面。這些未被用到的選擇器就像是多餘的行李,拖著走路不會快。
  • 在 JavaScript 裡寫了一堆測試用函式,或是舊版 API 的封裝,最後忘了刪除。這些死程式碼會被編譯後仍佔據記憶體,對頁面效能造成負擔。

同樣地,即使一張圖片尺寸很小,如果在 CSS 或 JS 裡引用了多次,而實際上只顯示一次,也是一種資源浪費。

如何一步步剃除不需要的程式碼?

  • 先找出:使用瀏覽器開發者工具或專門的分析插件,檢查哪些 CSS 選擇器、JS 函式真的被執行。舉例來說,在 Chrome 的「Performance」面板裡可以看到函式呼叫堆疊。
  • 刪除冗餘:把未被使用的程式碼從原始檔案中移除,或是設定編譯工具只輸出實際需要的部分。常見做法有「tree‑shaking」來剃除不會被引用的模組。
  • 測試驗證:執行自動化測試(如 Cypress 或 Jest)確保刪除後頁面功能正常,並重新載入速度指標確認是否提升。若發現頁面出錯,再回到原始碼檢查依賴關係。

這樣做的好處不止是縮小檔案大小:

  • 渲染更快:瀏覽器只需要解析、下載真正用到的程式碼,減少 CSS 選擇器匹配時間。
  • 維護成本降低:簡潔的程式碼更容易閱讀、除錯,也能讓團隊快速定位問題。

舉個實際例子:假設你有一個專案使用了 Webpack,透過設定 mode: 'production' 以及 optimization.treeShake: true,Webpack 就會自動剃除未被引用的 ES6 模組。

推薦工具與實作技巧

  • PurgeCSS:掃描 HTML、JS 或模板檔案,找出真正使用到的 CSS 選擇器,再生成乾淨的樣式表。常配合 Tailwind 使用,可把大約 80% 的樣式碼剃除。
  • ESBuild / Rollup:這兩個打包工具都支援 tree‑shaking,且編譯速度比 Webpack 快很多。只要在 build 指令裡加上 --minify,就能得到精簡的 JS。
  • UglifyJS 或 Terser:進一步壓縮 JavaScript 內容,例如移除註解、變數重命名,減少檔案大小。
  • CSSNano:對 CSS 做深度優化,包括合併規則、去掉冗餘屬性等。將多個 CSS 檔案打包成一個,再用 CSSNano 壓縮,可降低 HTTP 請求數量。

實際操作時,可以把這些工具串接到 CI/CD 流程,讓每次提交都自動執行「剃除」步驟。舉例來說,在 GitHub Actions 裡寫一段腳本:

  • 先跑 npm run build(使用 Rollup + Terser)。
  • 接著執行 purgecss --config purgecss.config.js
  • 最後把產出的檔案部署到 CDN。這樣就確保了每個版本都經過嚴格的資源清理,載入速度更順暢!

延遲載入非核心腳本,先顯示重要內容

「延遲載入非核心腳本,先顯示重要內容」是提升網頁速度、改善使用者體驗的關鍵手法。透過把不影響首屏渲染的 JavaScript 延後執行,可讓瀏覽器更快完成畫面呈現。
這篇文章將說明延遲載入的原理、實作方式,以及常見陷阱,最後提供一個完整範例給你參考。

延遲載入非核心腳本,先顯示重要內容

在網頁首次渲染時,瀏覽器會優先下載並執行對畫面有直接影響的 CSS 與 HTML。若同一時間還同步下載大量 JavaScript,往往會拖慢首屏呈現速度。延遲載入非核心腳本,就是把這些不必立即執行的程式碼推到頁面已經顯示完畢之後再去請求,讓使用者能更快看到關鍵內容。

為什麼要延遲載入?
  • 提升首屏渲染速度,減少跳動感。
  • 降低 CPU 與記憶體負擔,特別是行動裝置。
  • 讓搜尋引擎更容易抓取重點內容,提高 SEO 表現。
如何實作?
  • 利用 <script>asyncdefer 屬性。
  • 動態產生 <script> 標籤,等頁面完全載入後再插入。
  • 使用 IntersectionObserver 觀察畫面外的區塊,僅當使用者即將滾動到該區時才載入腳本。
1. asyncdefer
  • async:腳本下載完成後立即執行,可能會打亂執行順序。適用於獨立功能,例如分析程式。
  • defer:腳本下載完畢後,等 DOMContentLoaded 後才依載入順序執行。可確保腳本之間的相依關係。
<script src="https://example.com/analytics.js" async></script>
<script src="https://example.com/widget.js" defer></script>
2. 動態載入腳本
  • window.onload 或自訂事件中動態產生 <script>,確保頁面已完全呈現。
window.addEventListener('load', function () {
  var script = document.createElement('script');
  script.src = 'https://example.com/interactive.js';
  script.defer = true;
  document.body.appendChild(script);
});
常見陷阱與注意事項
  • 過度使用 async 可能導致 API 呼叫順序錯亂。
  • 某些第三方 SDK(如廣告)需要在頁面最前載入,否則會失效;這時可考慮將其放置於 <noscript> 或條件載入。
  • 動態載入腳本後,要確保相關變數或函式已經在全域命名空間中註冊,以免後續程式碼找不到。
範例:一個簡易的延遲載入實作
  • 假設我們想要在使用者滾動到頁面底部時才載入聊天窗口腳本,以下示範如何結合 IntersectionObserver 與動態載入。
var target = document.getElementById('chat-trigger');
var observer = new IntersectionObserver(function (entries) {
  if (entries[0].isIntersecting) {
    var script = document.createElement('script');
    script.src = 'https://example.com/chat.js';
    document.body.appendChild(script);
    observer.unobserve(target);
  }
});
observer.observe(target);

這樣使用者在滾動到指定區塊前不會下載聊天腳本,節省帶寬與執行時間。

參考連結

圖片優化技巧:不犧牲畫質的同時縮小檔案大小

選擇合適格式並壓縮圖片,減少載入時間

先了解不同檔案格式的特性,挑選最適合的類型才是省時又省流量的關鍵。

JPEG:照片最佳選擇

JPEG 以有損壓縮著稱,畫質相對較好,但在需要透明背景或圖形文字時並不理想。

PNG:透明與線條圖最愛

PNG 無損壓縮,保留透明通道,是 logo、按鈕等純色區塊的首選。

WebP:兼顧畫質與檔案大小

WebP 由 Google 推出,支援有損和無損兩種模式,可比 JPEG 小 25%~35%,但不被所有瀏覽器完全支援。

AVIF:最新高效格式

AVIF 在同等畫質下可比 WebP 更小,但目前支援度還在提升中,適合未來導入。

接著談談怎麼實際壓縮圖片,不管你是開發者還是設計師,都能從中省下不少時間。

使用工具:ImageMagick 來自動化

  • 安裝完成後,執行 convert input.jpg -quality 80 output.jpg 可將 JPEG 壓縮至 80% 質量。
  • 若想保留透明度,可用 convert input.png -strip -define png:compression-level=9 output.png;這會去除 EXIF、降低壓縮級別,減少檔案大小。

在線服務範例:TinyPNG、CompressJPEG

  • TinyPNG 只支援 PNG/JPEG,但其自動優化演算法能將檔案平均下降約 50%。
  • CompressJPEG 可一次上傳多張圖片,並且提供不同品質等級選項。

前端自動化:gulp-imagemin、webpack-image-minimizer

  • 在 gulp 裡加 imagemin 任務,只需一行設定即可自動壓縮所有圖片。
  • webpack 的 image-minimizer-webpack-plugin 讓你在打包時就完成壓縮,省去手工步驟。

實際案例:從 2MB 到 350KB,只需三個小技巧

  • 原始圖片為 1920x1080 的風景照,檔案大小 2,100KB。
  • 先轉成 WebP,品質設定 80%,得到 850KB。
  • 再使用 ImageMagick -strip -quality 70,最終尺寸 350KB。

步驟一:確定用途與格式

如果是照片 → JPEG 或 WebP;若有透明度或線條圖 → PNG 或 WebP 無損。

步驟二:調整尺寸

先把圖片縮小到最適合的顯示尺寸,避免瀏覽器再做放大/縮小。

步驟三:選擇壓縮工具與品質

根據需求設定品質值;一般網站可用 70-80% JPEG,WebP 可設 75%。

步驟四:檢測載入時間

使用 Chrome DevTools 的 Network 面板,確認圖片大小與下載時長。

步驟五:備援格式(可選)

<picture> 標籤中提供 WebP 與 fallback JPEG/PNG,以確保舊版瀏覽器也能正常顯示。

用 SVG 取代位圖:解析度無限又省檔案

用 SVG 取代位圖:解析度無限又省檔案

在網頁開發中,圖片往往是影響載入速度的主要瓶頸之一。傳統位圖(如 PNG、JPG)雖然易於製作,但一旦需要放大、縮小或做動畫處理,就會出現像素化或檔案急劇膨脹的問題。

SVG(Scalable Vector Graphics)是一種基於 XML 的向量圖形格式,擁有以下幾個顯著優點:

  • 解析度無限:不管放大多少倍,只要瀏覽器能繪製,就永遠保持清晰。
  • 檔案體積小:對於簡單圖形(如按鈕、圖示),SVG 的字串長度往往比同等畫質的 PNG 或 JPG 小 50%~90%。
  • 可直接在 CSS / JS 操作:可以改變顏色、透明度、大小,甚至做簡易動畫,而不需要重新產生圖片。
如何將位圖轉成 SVG
  1. 使用向量化工具(如 Adobe Illustrator、Inkscape):
  • 將 PNG 或 JPG 匯入後,使用「路徑追蹤」(Image Trace)功能自動生成向量路徑。
  • 調整滑桿參數,確保線條平滑且節點數目不過多,避免檔案膨脹。
  1. 手工繪製
  • 對於簡單圖示(logo、按鈕),直接在向量編輯器中畫出路徑即可。
  1. 使用線上轉換工具
  • 例如「Vector Magic」或「Online-Convert.com」,可以一次性批次將多張圖片轉成 SVG,並提供簡易的壓縮選項。
範例:把一個簡單按鈕圖示改寫為 SVG
原始 PNG(假設檔案大小 1.2KB)
  • 圖片內容:藍色圓形,白色字母 "A"。
導出的 SVG 範例
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" width="32" height="32">
  <circle cx="32" cy="32" r="30" fill="#1E90FF" />
  <text x="50%" y="55%" font-family="Arial, sans-serif" font-size="36" fill="#FFFFFF" text-anchor="middle">A</text>
</svg>
  • 檔案大小:僅約 0.3KB,比原始 PNG 大小縮減 75%。
在網頁中使用 SVG 的技巧
  1. 直接內嵌(如上例):最簡單的方式,適合少量圖示。

  2. SVG Sprites:將多個小圖示匯入一個大 SVG,利用 <use> 參考各自的 id

<svg style="display:none">
  <symbol id="icon-home" viewBox="0 0 64 64">…</symbol>
  <symbol id="icon-user" viewBox="0 0 64 64">…</symbol>
</svg>

<!-- 使用方式 -->
<svg width="32" height="32"><use href="#icon-home"></use></svg>
  1. CSS 取色:將 fillstroke 設為 currentColor,讓 SVG 隨著文字顏色改變。

  2. 懶加載:對於大量 SVG,可使用 Intersection Observer API 在畫面即將進入視窗時才插入 DOM。

常見問題與解答
  • Q: SVG 會不會跟位圖一樣失真?<br>A: 只要是純向量路徑,放大縮小都不會失真;若包含 raster image(如 <image> 標籤)則仍可能失真。
  • Q: 是否需要額外的 polyfill?<br>A: 現代瀏覽器已原生支援 SVG,舊版 IE9 之前需要 polyfill,但在大多數情況下不再必要。
小結

將位圖轉成 SVG 不僅可以節省載入時間,更能確保在任何解析度下保持畫面清晰。只要掌握好向量化流程、合理使用 <use> 與 CSS 控制,就能輕鬆打造既美觀又高效的網頁圖示。

實作挑戰:挑選你網站上一張 2KB 的 PNG 圖片,試著用 Inkscape 或線上工具轉成 SVG,並比較兩者檔案大小與畫質。

懶加載圖片,先載入可見區域的內容

懶加載圖片(Lazy Loading)是指只在使用者滾動到該區塊時才下載並顯示圖片,這樣能減少初次頁面載入的資源量。
透過設定 loading="lazy"、Intersection Observer 或第三方函式庫,網頁可以在需要時再請求圖檔,提升速度與使用者體驗。

懶加載圖片,先載入可見區域的內容

懶加載讓瀏覽器只在需要時才請求圖片,這樣初始頁面就能更快載完。以下列出三種實作方式:

  • loading="lazy" 屬性(最簡單)
  • Intersection Observer API
  • 第三方函式庫(如 lazysizes、lozad.js)

1️⃣ 直接使用 loading="lazy"

<img> 標籤加上 loading="lazy",瀏覽器會自動處理:

<img src="example.jpg" alt="說明文字" loading="lazy">

這種方式適合現代瀏覽器,設定簡單,但不支援所有舊版。

2️⃣ Intersection Observer 範例

Intersection Observer 可以更細緻地控制:

const images = document.querySelectorAll('img[data-src]');

const observer = new IntersectionObserver((entries, obs) => {
    entries.forEach(entry => {
        if (entry.isIntersecting) {
            const img = entry.target;
            img.src = img.dataset.src;
            obs.unobserve(img);
        }
    });
}, {rootMargin: '0px 0px 200px 0px'});

images.forEach(img => observer.observe(img));

使用 data-src 儲存真實檔案路徑,等到圖片進入可見範圍時才改成 src

3️⃣ 使用第三方函式庫

  • lazysizes:自動偵測並載入,支援延遲加載、懶加載背景圖。
  • lozad.js:輕量級,設定方式與 Intersection Observer 類似。

速度比較表格

方案 初始下載大小 兼容度 實作複雜度
loading="lazy" 最小 大多數現代瀏覽器 簡單
Intersection Observer 中等 現代瀏覽器 (IE 需要 polyfill) 中等
第三方函式庫 中等 視函式庫而定 較複雜

小結

懶加載不僅能減少首屏資源,還能讓使用者在瀏覽時感受到更順暢。根據專案需求選擇合適方案即可。

根據裝置尺寸提供不同解析度的圖片

在網頁開發中,圖片往往佔用大量資源。若不加以控制,會拖慢載入速度、浪費帶寬。

本篇將教你如何根據裝置尺寸提供不同解析度的圖片,既保持畫質,又能縮小檔案大小,提升使用者體驗。

什麼是響應式圖像

響應式圖像允許瀏覽器根據裝置寬度選擇最適合的檔案,避免因載入過大圖片造成速度下降。

為何重要

  • 節省帶寬:手機、平板只下載必要大小的圖片。
  • 提升載入速度:較小檔案能更快呈現。
  • 維持畫質:在高解析度裝置顯示清晰,低解析度裝置避免雜訊。

如何實作

1. 使用 <picture> 元素
<picture>
  <source media='(min-width: 800px)' srcset='image-large.jpg'>
  <source media='(min-width: 400px)' srcset='image-medium.jpg'>
  <img src='image-small.jpg' alt='示例圖片'>
</picture>
2. srcsetsizes 屬性
<img src='small.jpg'
     srcset='medium.jpg 800w, large.jpg 1200w'
     sizes='(max-width: 600px) 100vw, 600px'>

此範例讓瀏覽器根據視窗寬度自動挑選合適檔案。

3. 圖片格式選擇 (WebP / AVIF)
格式 支援瀏覽器 壓縮率
JPEG 所有主流瀏覽器 中等
WebP Chrome, Edge, Firefox
AVIF 近年新版瀏覽器 更高

工具與資源

  • ImageMagick:批次轉檔、壓縮。
  • Squoosh:線上圖片最佳化工具,支援 WebP/AVIF。
  • Cloudinary / Imgix:自動產生多尺寸、格式的 CDN 服務。

小結

透過 <picture>srcset 配合適當的圖片格式,你可以在不犧牲畫質的前提下,為不同裝置提供最佳化的載入體驗。

快取策略實作:一次載入,永遠不重複

設定 HTTP Cache-Control 標頭,讓瀏覽器自行快取

HTTP Cache-Control 標頭是什麼?

這個標頭告訴瀏覽器,對於某些資源可以存到本地快取裡面,以後再來時就不用重新下載。

舉例:你剛上完一個網站的首頁,之後如果再次打開同一頁,瀏覽器會直接從快取讀取,而不是重新請求網頁。

Cache-Control 的常用指令

  • no-cache:表示即使有快取,也必須先跟伺服器確認一次內容是否變更。
  • max-age=秒數:告訴瀏覽器這個資源可以在多久時間內保持有效,例如 max‑age=86400 表示一天。
  • public / private:public 允許任何快取機制存檔;private 僅限使用者自己的快取。
  • must-revalidate:當快取已過期時,必須再次跟伺服器確認才行。

在不同伺服器設定範例

Apache(.htaccess)
  • 針對圖片、CSS、JS 設定一天的快取:
AddDefaultCharset UTF-8
<FilesMatch "\.(jpg|jpeg|png|gif|css|js)$">
    Header set Cache‑Control "public, max‑age=86400"
</FilesMatch>
Nginx
  • 在 server 區塊加入:
location ~* \.(jpg|jpeg|png|gif|css|js)$ {
    expires 1d;
    add_header Cache‑Control "public, max‑age=86400";
}

小技巧與注意事項

  • 把靜態資源(圖片、CSS、JS)設定長期快取,動態頁面則盡量使用 no-cache 或短時間。
  • 需要更新的檔案改名或加上版本參數,例如 style.css?v=2.1,這樣瀏覽器會重新下載。
  • ETag 搭配 Cache‑Control 可以更精細地控制快取行為;但大多情況下只要設定 max‑age 就夠用。
  • 在開發階段可以先把 Cache‑Control 設成 no-store,確定內容無誤後再改回正式值。

利用 Service Worker 實作離線快取與預抓取

利用 Service Worker 實作離線快取與預抓取

在這篇教學裡,我們會一步步帶你完成一個簡單又實用的 Service Worker,讓網站不管是網路還是離線,都能順利顯示。

步驟一:註冊 Service Worker
  • 在主程式碼(通常是 index.html 或主 JavaScript 檔)裡加入以下語法,確保瀏覽器支援後再執行。
if ('serviceWorker' in navigator) {
  window.addEventListener('load', () => {
    navigator.serviceWorker.register('/sw.js').then(reg => {
      console.log('[SW] 註冊成功:', reg.scope);
    }).catch(err => {
      console.error('[SW] 註冊失敗:', err);
    });
  });
}
步驟二:安裝階段緩存靜態資源
  • sw.js 是 Service Worker 的主檔案,下面示範如何在 install 事件中一次性把核心資源加入快取。
const CACHE_NAME = 'app-static-v1';
const ASSETS_TO_CACHE = [
  '/',
  '/index.html',
  '/styles.css',
  '/main.js',
  '/logo.png'
];

self.addEventListener('install', event => {
  console.log('[SW] 安裝階段開始');
  event.waitUntil(
    caches.open(CACHE_NAME).then(cache => {
      return cache.addAll(ASSETS_TO_CACHE);
    })
  );
});
步驟三:攔截請求並回傳快取或網路資料
  • fetch 事件中,我們先嘗試從快取拿資料,如果沒有就直接去網路抓。
self.addEventListener('fetch', event => {
  const request = event.request;
  // 只處理 GET 請求,避免 POST 等動作被快取影響
  if (request.method !== 'GET') return;

  event.respondWith(
    caches.match(request).then(cachedResponse => {
      if (cachedResponse) {
        console.log('[SW] 回傳快取:', request.url);
        return cachedResponse;
      }
      // 沒有快取時,從網路抓並同時把結果存進快取(可選)
      return fetch(request).then(networkResponse => {
        const clone = networkResponse.clone();
        caches.open(CACHE_NAME).then(cache => cache.put(request, clone));
        console.log('[SW] 從網路取得:', request.url);
        return networkResponse;
      });
    })
  );
});
步驟四:處理快取版本更新
  • 當你改版時,只要把 CACHE_NAME 改成新的名稱,舊的快取就會在 activate 階段被清除。
self.addEventListener('activate', event => {
  const allowedCaches = [CACHE_NAME];
  event.waitUntil(
    caches.keys().then(cacheNames => {
      return Promise.all(
        cacheNames.map(name => {
          if (!allowedCaches.includes(name)) {
            console.log('[SW] 刪除舊快取:', name);
            return caches.delete(name);
          }
        })
      );
    })
  );
});
預抓取技巧 & 進階設定
  • 導航預載 (Navigation Preload):在瀏覽器支援時,先把主文件(如 index.html)的請求交給瀏覽器執行,等到 Service Worker 啟動後再將回應合併。這樣可以減少首次載入延遲。
self.addEventListener('activate', event => {
  if ('navigationPreload' in self.registration) {
    self.registration.navigationPreload.enable();
  }
});

self.addEventListener('fetch', event => {
  const { request } = event;
  if (request.mode !== 'navigate') return; // 只處理導航請求

  event.respondWith(
    caches.match(request).then(cached => {
      if (cached) return cached;
      return fetch(request); // 若快取沒命中,直接網路抓
    })
  );
});
  • 背景預抓取:利用 Background Fetch API(目前仍在實驗階段),可以在離線時把大型資源先下載完成,等到使用者再次上線時直接從快取讀取。
快取結構表
快取名稱 儲存內容 失效策略 備註
app-static-v1 index.html, styles.css, main.js 等靜態檔 手動版本更新 建議每次改版都換名
images-cache 圖片、圖示等 30 天後刪除 可用 cache.put 時加上過期時間
api-cache API 回應 JSON 1 小時 適合頻繁更新的資料
  • 注意事項

    • 每個主機域名最多只能存 5-10 MB,需定期清理舊快取。
    • 若使用 cache.addAll 把大量檔案一次加入,請確保檔案都能成功下載,否則整個安裝會失敗。
  • 實際案例
    假設你開發的是一個旅遊資訊網站,你可以把首頁、目的地列表、熱門景點圖片等放入 app-static-v1;同時把天氣 API 的回應快取到 api-cache,每次載入頁面時先從快取拿資料,再不斷更新。

  • 測試離線功能:在 Chrome 開發者工具(Application 面板)開啟「Offline」模式,即可確認 Service Worker 是否能正常提供快取內容。

定期清理舊版快取,避免舊資源卡住

定期清理舊版快取,避免舊資源卡住

在開發網站或網頁應用時,我們往往會把 CSS、JavaScript 與圖片等靜態檔案放進快取,讓瀏覽器每次直接從本地讀取而不用再去伺服器下載。這樣可以大幅減少載入時間。

然而,當你更新程式碼或圖片後,如果舊的快取還在,使用者看到的可能仍是過時的內容,甚至會因為資源不一致而出現錯誤。定期清除舊版快取能確保每個人都得到最新、最正確的頁面。

以下提供實作步驟與範例,教你怎麼把這項工作自動化,讓維護變得輕鬆又可靠。

步驟一:使用版本號或哈希值命名檔案

  • 在產生靜態資源時,將內容的 SHA1、MD5 或簡單版號附加到檔名,例如 style.3f2a.cssapp.v2.js
  • 這樣即使檔案更新,瀏覽器也會認為是全新的檔案,直接下載,而不是使用舊快取。

範例:

# 在 webpack 設定中加入 output.filename
output: { filename: '[name].[contenthash].js' }

步驟二:設定適當的 Cache-Control 標頭

  • 為靜態資源加上 Cache-Control: max-age=31536000, immutable,表示可快取 1 年且內容不可改變。
  • 當檔名更換時,舊快取會自動失效;若檔名不變,則仍然使用快取。

範例:

# Nginx 設定靜態資源
location ~* \.js$ {
    add_header Cache-Control "max-age=31536000, immutable";
}

步驟三:利用服務工作者(Service Worker)做更進階的快取清理

  • install 階段安裝新版本時,使用 self.skipWaiting() 讓新的 Service Worker 立即接管。
  • activate 階段,用 caches.keys() 列出所有舊快取,再逐一刪除不再需要的快取。

範例:

// service-worker.js
self.addEventListener('install', event => {
  self.skipWaiting();
});

self.addEventListener('activate', event => {
  const currentCaches = ['static-v2']; // 新快取名稱
  event.waitUntil(caches.keys().then(cacheNames => {
    return Promise.all(
      cacheNames.filter(name => !currentCaches.includes(name)).map(name => caches.delete(name))
    );
  }));
});

步驟四:自動化腳本與 CI/CD 整合

  • 在部署流程中加入清除舊快取的指令,例如使用 Cloudflare Workers KV 或 GitHub Actions 的 Action。
  • 也可以在伺服器端設定一個 cron job,每天凌晨 3 點跑一次 rm -rf /var/www/cache/*,確保舊檔案不會長時間佔用磁碟空間。

範例:

# 每日清理快取的 cron job 範本
0 3 * * * /usr/local/bin/clean-cache.sh

# clean-cache.sh
#!/bin/bash
rm -rf /var/www/cache/*
echo "Cache cleaned at $(date)" >> /var/log/cleanup.log

小結:把清理流程寫進日常部署,讓舊資源永遠不會卡住

  • 只要在每次更新時改檔名或加上 hash,就能自動讓瀏覽器抓到最新檔案。
  • 若你使用 Service Worker 或 CDN,別忘了同步更新快取名稱,並在 activate 階段把舊快取刪除。
  • 最後,把清理腳本掛上排程或 CI/CD pipeline,確保不會遺漏。

使用 hash 或版本號,確保更新內容被載入

使用 Hash 或版本號確保更新內容被載入

在網頁快取策略中,最常見的問題是:使用者下載了舊版資源後,即使伺服器端已經更新,瀏覽器仍然會從快取裡讀取舊檔。這不僅影響使用者體驗,也可能導致錯誤顯示或功能失效。

解法很簡單:給每一個靜態資源(CSS、JS、圖片等)加上「hash」或「版本號」。當內容發生變化時,檔名也改動;瀏覽器因為找不到舊的快取鍵值,就會重新請求最新檔案。這樣就能確保一次載入後永遠不重複使用過期資源。

為什麼要加 hash / 版本號?
  • 避免瀏覽器快取舊檔:瀏覽器會把同名檔案視為相同,若沒有變更標記就不會重新下載。
  • 簡化 CDN 或代理伺服器的設定:只要改檔名即可無縫切換,不需清除快取或設定過期時間。
  • 減少錯誤風險:舊版腳本載入時可能呼叫新 API,導致 JavaScript 錯誤。變更檔名即刻解決。
常見做法
  • 內容 hash:利用 MD5、SHA1 等演算法把檔案內容轉成雜湊值,再作為檔名或參數,確保同一內容永遠有相同 hash。
  • 版本號:在專案發佈時手動維護 v1.2.3 或日期,例如 app.v20240819.js。更新後改版號即可。
範例:使用 webpack 產生 hash 檔名
  • webpack.config.js 中設定 output.filename[name].[contenthash].js
  • 這樣輸出的檔案名稱會包含內容雜湊,例如 main.4f2a1c3b.js

實作步驟:從設定到部署

  • 步驟一:建置工具設定
// webpack.config.js
module.exports = {
  entry: './src/index.js',
  output: {
    filename: '[name].[contenthash].js', // 加入 hash
    path: __dirname + '/dist',
    clean: true, // 每次建置前清空 dist 資料夾
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: './src/index.html',
      filename: 'index.html',
    }),
  ],
};
  • 步驟二:HTML 引用方式

使用 HtmlWebpackPlugin 可以自動把生成的 hash 檔名寫進 <script><link>。如果你是手動管理,可以在模板裡加入 {{ assetHash }} 或類似佈景符號。

  • 步驟三:部署到 CDN

將整個 dist/ 資料夾上傳至 CDN,確保檔名不被改寫。若使用 CloudFront、Azure CDN 等,直接把對應路徑指向這些檔案即可。

  • 步驟四:測試
  1. 在瀏覽器開啟網頁並執行 F12 觀察 Network 面板。
  2. 修改一個 JS 檔案,重新建置,發現檔名已變動(如 main.4f2a1c3b.jsmain.c8d6e7f9.js)。
  3. 清除瀏覽器快取或使用隱身模式再次載入,確定新檔案被下載。
  • 步驟五:維護

若你不想每次都改 hash,可以選擇「版本號」方式:

// webpack.config.js(簡易版)
const version = 'v1.2';
module.exports = {
  output: {
    filename: `[name].${version}.js`,
  },
};

只要在發佈時改 version 就行。

小結
  • Hash 方式能確保任何內容變動都會產生新檔名,最安全且自動化。
  • 版本號則更易於人閱讀與手動管理,適合需要快速迭代但不想每次重建整個專案的情境。

選擇哪一種方式取決於團隊工作流程,但兩者目的相同:讓瀏覽器永遠載入最新、正確的資源。

瀏覽器渲染優化:讓畫面更快顯示的技巧

提取關鍵 CSS,先顯示重要內容

提取關鍵 CSS,先顯示重要內容

在網頁載入時,如果所有樣式表都要等到下載完畢才能渲染畫面,使用者會看到一個空白或延遲的視覺體驗。這種情況常被稱為「阻塞渲染」,尤其是當 CSS 檔案很大時。
解決辦法之一就是先把最重要、最先看到的樣式提取出來,直接寫在 <head> 裡面,讓瀏覽器可以馬上顯示關鍵內容;剩下的樣式再非同步載入。

1️⃣ 如何挑選關鍵 CSS

一開始先打開 Chrome 的 DevTools,切到「Performance」面板,重新整理頁面並記錄起始幾秒的渲染流程。
在 Timeline 裡找到 "Render" 或 "Paint" 標籤,觀察哪些元素被最早畫出。把對應的 CSS 選取起來,就能知道關鍵樣式是什麼。

2️⃣ 自動化工具

常見的開源工具有:

3️⃣ 實際操作範例

假設我們有一個 index.html,裡面引用了 main.css(200KB)。使用 critical 指令可以這麼做:

npx critical src/index.html -o dist/index.html --width=1300 --height=900

執行完後,dist/index.html 裡的 <head> 會自動變成:

<head>
  <meta charset="utf-8">
  <title>我的網站</title>
  <style>/* 這裡是提取出的關鍵 CSS */
    body{font-family:Arial,Helvetica,sans-serif;}
    .hero{height:400px;background:#f5f5f5;}
    /* …其他首屏樣式… */
  </style>
  <link rel="preload" href="main.css" as="style" onload="this.rel='stylesheet'">
  <noscript><link rel="stylesheet" href="main.css"></noscript>
</head>

這樣做之後,瀏覽器先顯示關鍵內容,等到 main.css 完全下載再套用其他樣式。

4️⃣ 小技巧與注意事項

  • 避免重複:確保提取的 CSS 不包含在後續外部檔案中,以免造成多餘下載。
  • 測試兼容性:不同瀏覽器對 preload 的支援略有差異,建議加上 <noscript> fallback。
  • 監控效果:使用 Lighthouse 或 WebPageTest 觀察首次繪製(FCP)與內容完整載入(LCP)的變化。

預留尺寸,避免畫面跳動 (CLS)

在網路頁面中,畫面跳動(CLS)會讓使用者感到不舒服。本文將說明為什麼要預留尺寸,以及如何透過簡單的做法降低 CLS 分數。
接下來,我們會舉例說明常見的問題來源、測試工具與實際優化步驟,幫你快速上手。

什麼是 CLS?

CLS(Cumulative Layout Shift)指頁面載入時元素位置發生變動的累積分數,Google 在 Core Web Vitals 中將其列為重要指標。高 CLS 會降低使用者體驗與搜尋排名。

為什麼要預留尺寸?

  • 避免重排:瀏覽器還不知元素大小時,載入內容後才調整位置,造成跳動。<br/>- 提升可預測性:使用者在等待時看到完整佈局,不會因圖片或字型突然改變而分心。

常見尺寸未預留的情境

  • 圖片(無 width/height)<br/>- 動畫影片 (iframe 內嵌)<br/>- Web 字型延遲載入<br/>- 外部資源載入後才決定排版

如何預留尺寸?

  • 圖片:在 <img> 標籤加入 widthheight,或使用 CSS 佔位區塊。<br/>- 影片:設定 iframe 或 video 的寬高屬性,例如 <iframe width="560" height="315"></iframe>。<br/>- 字型:利用 font-display: swap 或在 CSS 加上 @font-face { font-family: 'MyFont'; src: url('myfont.woff2') format('woff2'); font-display: swap; },確保文字佔位不變。<br/>- 動態內容:使用 CSS 佔位符(如 .placeholder{height:200px;background:#f0f0f0;})先留空間。

實作範例

  • 圖片範例: <img src="hero.jpg" width="1200" height="600" alt="英雄圖片">
  • 影片範例: <iframe src="https://www.youtube.com/embed/abc123" width="560" height="315"></iframe>
  • 字型範例: @font-face { font-family: 'NotoSans'; src: url('NotoSans.woff2') format('woff2'); font-display: swap; }
  • CSS 占位符範例: .placeholder{width:100%;height:200px;background:#e0e0e0;}

測試工具與指標檢查

  • Chrome DevTools:Performance 面板中的 Layout Shift 數據。<br/>- Lighthouse:報告中 CLS 分數。<br/>- Web Vitals Extension:即時監測。

常見陷阱與解決方法

  • 未設定高度的 SVG:確保 <svg> 加上 heightwidth 或使用 CSS 佔位。<br/>- 字型重排:若使用 font-display: optional,仍需留足空間避免跳動。

小結

預留尺寸是減少 CLS 的最直接方法。只要在圖片、影片、字型與動態內容前先確定佔位,就能大幅提升頁面穩定度,讓使用者更順暢地瀏覽網頁。

減少重排次數,提高渲染效能

減少重排次數,讓畫面更順暢

在瀏覽器渲染時,最耗資源的動作往往是 重排(reflow)。每當改變元素尺寸、位置或樣式時,瀏覽器都必須重新計算佈局。這個過程不但會卡住畫面,也會拖慢整體效能。

以下整理了幾種實際可用的技巧,讓你在開發中能有效減少重排次數,進而提升頁面流暢度。

6. 刪除不必要的樣式計算

避免使用複雜的 CSS 選擇器或過度嵌套,讓瀏覽器更快匹配元素。簡化結構可直接降低重排開銷。例如,把 ul > li > a 換成 .nav-link

<ul class="nav">
  <li><a href="#" class="nav-link">首頁</a></li>
  <li><a href="#" class="nav-link">關於我們</a></li>
</ul>

使用 async 或 defer 讓腳本非阻塞載入

使用 async 或 defer 讓腳本非阻塞載入

在網頁載入時,瀏覽器會根據 <script> 標籤的位置順序下載並執行 JavaScript。若沒有額外設定,這個過程就像排隊買票,一旦遇到腳本就停下來等它完成,直到腳本結束才繼續往後載入其他資源。

這種阻塞行為會影響頁面第一次顯示時間(First Paint)與互動性(Time to Interactive),尤其是大型框架或第三方庫。

async 與 defer 兩大選項

<script> 標籤可加入 asyncdefer 屬性,讓瀏覽器在下載腳本時不必停下來等待。這裡簡單說明兩者差異:

  • async: 允許瀏覽器同時下載並執行腳本,腳本完成後立即執行。若有多個 async 腳本,它們的執行順序可能跟下載結束時間無關。

  • defer: 先下載再排隊等待頁面解析完畢(DOM ready)之後才一次性執行,並且保持腳本載入順序。這樣就能確保依賴關係不會被打亂。

實際寫法範例

<script src="https://cdn.example.com/lib.js" async></script>

<script src="https://cdn.example.com/main.js" defer></script>

若你想同時使用兩種屬性,只能選一個;但在 HTML5 之後,標籤可以重複使用不同的腳本。

選擇建議
  • 非依賴關係 的第三方腳本(例如 Google Analytics、社群分享按鈕)可加 async;它們不需要等其他腳本執行完畢就能動作。

  • 需要保持腳本執行順序或與 DOM 互動的程式碼建議使用 defer

  • 若你有多個 script 並且想同時下載但又不介意執行順序,亦可選擇 async

常見陷阱
  • 同步腳本(沒有 async 或 defer)仍會阻塞渲染;若把它放在 <head> 內,頁面可能卡頓數秒。

  • 動態插入腳本:使用 document.createElement('script') 時,需要手動設定 asyncdefer,否則預設為同步。

測試方法
  1. 打開 Chrome 開發者工具 → Network 面板,切換到「Disable cache」。

  2. 觀察腳本的下載時間與執行時機。你會看到 async 腳本在載入完成後立即顯示為「Finished」並執行;defer 則列隊等待 DOMContentLoaded。

  3. 在 Console 輸入 performance.timing.domContentLoadedEventEnd - performance.timing.navigationStart 來確認頁面可互動時間。

小結

透過合理運用 async 與 defer,能顯著減少因腳本阻塞造成的渲染延遲。記得:

  • async 適合獨立、可並行執行的腳本;
  • defer 保障順序且不阻塞解析;
  • <head> 放置重點腳本時,最好加上 defer,以免影響頁面首畫。