網站載入優化實戰攻略

網站載入優化實戰攻略

所謂網頁優化指的是縮短訪問網站時載入的時間,從而降低訪客跳出率,最終達到更好的 SEO 成效。一直以來這就是後端工程師覺得最棘手的工作之一,畢竟影響載入速度的原因很多,沒有較好的方法論很難找到可行的具體作法

這一篇文章我將與你分享我從最近的一個上市公司專案中學到,該如何有效提高效能,達到如下圖的成效。希望透過這篇文章,讓你再也不需要擔心網頁載入速度慢囉

Untitled

一如我技術文章的寫作風格以實用為主,後續的內容編排將按照你實作的順序來寫,建議你按照我文章的流程來優化你的專案

網頁優化工具

唯有知道你的網站有什麼問題才談得上該如何進行優化,所以首先介紹幾個我覺得好用的網頁優化工具給你,它們的免費功能就已經足以使用囉,也不需要你去註冊

WebPageTest

Untitled

雖然它的介面相比其他工具略顯老土,但仍為我最為推薦的工具,因為它免費版本就已經能提供你非常完整的資訊。當你按下圖的綠色按鈕後將會展開更詳盡的資訊讓你知道這個分數是如何算出,具體又是哪些地方出了問題,讓你非常容易明白哪裡出了問題

Untitled

另外測試伺服器支援台灣地區,也是我個人覺得很棒的地方。在多數情況下,使用它就足以取得完整的分析報告

Pingdom Tools

Untitled

它的介面看起來更簡潔一些,分析後會把所有資訊整合在一個頁面上。同樣會為你各方面的表現打上分數,並說明改善這個分數的優化方向,但缺點是不會指出你網站的具體問題是什麼,而是提供你一個技術文章告訴你問題大致有哪些原因以及可能的解決方案

Untitled

這個工具較為適合用在對網站具體問題較為了解,只需要快速有個概覽的情境

參考資料

WebPageTest - Website Performance and Optimization Test

Pingdom Tools


有了工具來幫你進行診斷,接著我來說說你基本需要做哪些優化工作,我是按照它對載入速度的影響比重來進行排序

圖片優化

從我的專案去觀察可以發現,影響載入速度最關鍵就在於圖片,大部分的載入時間都花在下載圖片上,絕對是需要優先改善。不過這多半關乎平面設計師如何處理圖片以及用戶如何上傳圖片,我們能做的就是訂出圖片使用政策,請大家共同配合

圖片尺寸

殺雞別用牛刀,如果圖片在網頁上呈現就如下圖左邊這樣的大小,就不應該上傳右邊這麼大的圖。他完全無助於圖片的清晰度與美觀,只是白白讓客戶等得更久

Untitled

圖片格式

目前較為常見的格式有 gif . png 與 jpg。 gif 檔案最大, png 次之,jpg 則最小

gif 格式應用於顯示動態圖片,就像是播放影片一樣

png 格式應用於需呈現透明的情況,比如 Favicon

如果沒有以上的需求,則建議都使用 jpg 讓圖片在相同解析度下把檔案變的最小。另外,現在 Google 也會建議你改用最新的圖片格式 webp ,能得到比 jpg 還要小的壓縮效果。jpg 檔案可以透過網頁服務來快速轉成 webp 格式,非常簡單

圖片壓縮

如果圖片格式為 jpg ,就能夠進行圖片壓縮,在相同呈現品質下把檔案變的更小。我使用的電腦是 Mac ,有一個免費軟體叫做 ImageOptim ,你只需要將檔案拖入工作區,剩下的就可以交給他幫你處理,非常方便

Untitled

參考資料

Graphical Content | Web Fundamentals | Google Developers

JPG to WEBP | CloudConvert

ImageOptim - better Save for Web


將圖片進行 lazy load

當圖片顯示時才進行載入,從而減少網頁渲染的時間。也就是說先讓用戶看到文字以及樣式,圖片先以灰底來替代,等到圖片載入之後才替換

我用的是 jQuery 的 LazyLoad 函式庫,以下說明實作的步驟

Step 1.

導入 jQuery LazyLoad Library

<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery.lazyload/1.9.1/jquery.lazyload.min.js"></script>

Step 2.

載入上方函式庫後,加入以下程式碼,請特別注意程式碼的順序

<script>
    $(document).ready(function(){
      $('img').lazyload();
    });
</script>

Step 3.

將圖片標籤的 src 屬性改成如下

<img data-original="請替換成你的圖片網址" >

PS:我發現某些套版介面使用上面的這種做法會導致衝突,無法正常的載入圖片。後來我改用的新解決方法更簡單,不需要使用任何的函式庫,直接用 HTML5 的新屬性 loading 即可,請參考如下:

<img src="請替換成你的圖片網址"  loading="lazy">

參考資料

jquery.lazyload - Libraries - cdnjs - The #1 free and open source CDN built to make life easier for developers


先載入樣式

當用戶在下載 head 標籤內容時是看不到任何內容的,只有下載到 body 標籤後才會在畫面上看到東西。因此應該把樣式表的載入放在 head 標籤內,而把 JS 的載入放在 body 標籤的最後(因為當客戶看不到畫面的時候根本也無法進行任何互動,不如先載入顯示相關的內容)

Untitled


減少請求次數

要知道除了網頁文件之外,網頁所用到的 CSS . JS 以及圖檔等等都要另外向伺服器來提出請求,哪怕檔案再小都一樣,減少請求次數可以省去標頭傳輸量 . 降低伺服器的負擔,還可以提高載入速度

如果網站有前端工程師一起配合的話,建議和他/她一起討論一下如何進行檔案瘦身,把不需要的樣式與腳本給移除,這一步應該做在壓縮檔案之前喔

具體的作法是將多個樣式表或 JS 腳本壓製成單一檔案,還能壓縮掉空格以及斷行,讓檔案變的更小,我是透過 Laravel 所自帶的 Compile Assets 功能來完成檔案壓縮,具體步驟如下:

Step 1. 編輯 webpack.mix.js

開啟專案的 webpack.mix.js ,編輯如下

//webpack.mix.js

const mix = require('laravel-mix');

/*
 |--------------------------------------------------------------------------
 | Mix Asset Management
 |--------------------------------------------------------------------------
 |
 | Mix provides a clean, fluent API for defining some Webpack build steps
 | for your Laravel applications. By default, we are compiling the CSS
 | file for the application as well as bundling up all the JS files.
 |
 */

mix.js('resources/js/app.js', 'public/js')
    .postCss('resources/css/app.css', 'public/css', [
    ]);

mix.styles([
    //將網頁用到的樣式檔案列在這裡,優先度用越下面
    'public/css/bootstrap.min.css',
    'public/css/jquery-ui.min.css',
        ...
], 'public/css/all.css');

mix.scripts([
    //將網頁用到的JS檔案列在這裡,被依賴的檔案要放上面
    'public/js/jquery-2.2.4.min.js',
    'public/js/jquery-ui.min.js',
        ...
], 'public/js/all.js');

Step 2.執行壓縮

開啟 Terminal ,輸入以下指令

壓縮多個檔案為一個並移除空格與斷行,用於正式環境
npm run production 

壓縮多個檔案為一個但保留空格與斷行,用於開發環境
npm run dev

延後載入與執行 JS

雖然把 JS 腳本放在 body 標籤的最後會讓畫面先渲染出來,但實際上載入還是未完全的,導致在 Google Chrome 的 lighthouse 插件跑分時還是不太好

下面這張圖是腳本與 HTML 渲染的示意圖,render html 為網頁渲染,script loading 為腳本下載,script execution 為腳本執行

  • 第一張圖是正常的腳本載入與執行方式,在載入與執行的過程中會暫停網頁渲染,也是分數最低的一種作法
  • 第二張圖是異步(Async)版本的方案,在載入時不再會暫停網頁渲染,等載入完就開始執行,較貼近第一張圖的腳本情境,但 IE 不支持,相容性較差
  • 第三張圖是 defer 版本的方案,在載入時同樣不再會暫停網頁渲染,腳本會在網頁渲染完成後才執行,能更大的縮短網頁載入時間,幾乎所有瀏覽器都支持,但執行時機被延到最後

https://www.marketingtracer.com/include/images/s/defer-javascript-en.png

接著,我說明該如何將腳本的載入改為 defer 方案, async 也是差不多,如果腳本是寫在另一個檔案內,你可以這樣寫

<script defer src="{{ asset('js/all.js') }}"></script>

要知道上面的寫法並不支持 inline script ,如果你把 jQuery 函式庫給 defer 載入之後,勢必會讓頁面上的 script 出問題。為解決這個問題,以下也說明你該如何調整 inline script

<script>
        window.addEventListener('DOMContentLoaded', function() {
            (function($) {
                //這裡可以撰寫你的 inline script
            })(jQuery);
        });

</script>

將網站內容進行 Gzip 壓縮

所謂 Gzip 壓縮指的是將網頁伺服器傳給瀏覽器的內容進行壓縮,必須要網頁伺服器有提供這個功能才行,我使用的是 A2Hosting 的 Shared Hosting ,它內建就有提供這個功能,預設是關閉的,需要你自己開啟

Untitled

Step 1.

進入 A2 Hosting 的 CPanel,路徑為 software > Optimize Website

Step 2.

將 Compress Content 改為 Compress All Content

檢查是否有作 GZIP 壓縮

GZIP Compression Test


標頭加上瀏覽器快取控制

如果訪客在初次訪問時能將檔案暫存於瀏覽器內,那麼當第二次訪問之後只要檔案尚未過期就不需要再重複進行下載,也就能加快載入速度

作法是在 public\.htaccess 檔案內,加入以下程式碼

//public\.htaccess

# BEGIN Expires-Headers
<IfModule mod_expires.c>
   ExpiresActive On
   AddType application/vnd.ms-fontobject .eot
   AddType application/x-font-ttf .ttf
   AddType application/x-font-opentype .otf
   AddType application/x-font-woff .woff
   AddType image/svg+xml .svg
   ExpiresByType application/vnd.ms-fontobject "access 1 year"
   ExpiresByType application/x-font-ttf "access 1 year"
   ExpiresByType application/x-font-opentype "access 1 year"
   ExpiresByType application/x-font-woff "access 1 year"
   ExpiresByType image/svg+xml "access 1 year"
   ExpiresByType text/html "access 1 hour"
   ExpiresByType text/css "access 14 days"
   ExpiresByType text/x-javascript "access 3 weeks"
   ExpiresByType application/javascript "access 1 month"
   ExpiresByType application/x-javascript "access 1 month"
   ExpiresByType image/gif "access 2 months"
   ExpiresByType image/png "access 2 months"
   ExpiresByType image/jpg "access 2 months"
   ExpiresByType image/jpeg "access 2 months"
   ExpiresByType image/gif "access 2 months"
   ExpiresByType application/pdf "access 1 year"
   ExpiresByType application/x-shockwave-flash "access 1 year"
   ExpiresByType image/x-icon "access 1 year"
   ExpiresDefault "access 2 days"
</IfModule>
# END Expires-Headers

# BEGIN Cache-Control-Headers
<ifmodule mod_headers.c>
   <filesmatch "(gif|ico|jpeg|jpe|jpg|svg|png|css|js)$">
      Header set Cache-Control "max-age=604800, public"
   </filesmatch>
</ifmodule>
# END Cache-Control-Headers

參考資料

HTTP Caching | Web Fundamentals | Google Developers


導入 CDN 快取

就像是餐廳的服務速度會受到店員的人數限制,每台伺服器所能夠提供的同時請求數也有限制。除了花錢去升級伺服器效能之外,另外一個可行的方法就是導入 CDN

CDN 能幫你處理下載靜態素材的請求,比如 css . js 與 html ,還能夠根據用戶所在區域來提供最近的伺服器。不但能減輕伺服器的請求負擔,也讓檔案下載變得更快

台灣最多人使用的 CDN 當屬 CloudFlare ,它所提供的免費方案就能滿足我們的需求。簡易作法是將網域加入 Cloudflare 讓它代管(它會先驗證你是否具有對該網域的控制權)

點擊要管理的網域之後,就能從 Caching 功能區塊的設定功能來設定快取,使用上非常直覺

Untitled


啟動 Laravel 快取

PHP 最讓人喜歡與最讓人頭痛的就在於每次請求都會重新編譯,開發雖然較為方便但是就會導致回應速度變慢

Laravel 的作法是透過檔案合併和 serialization 等機制來節省檔案載入數量與略過 route collection 和 blade 的編譯時間(多謝光賢大的補充),達到節省效能與時間的目的。根據官方文件的說明,這樣的做法能提高 20 倍的效率,因此專案上線之後建議務必開啟

開啟 Terminal ,輸入以下指令:

php artisan route:cache //建立路由快取
php artisan view:cache //建立視圖快取
php artisan config:cache //建立設定快取

啟動資料庫快取

除了編譯檔案可以快取,如果仍然必須要重新編譯,也可以考慮進行資料庫查詢結果的快取,這樣下次要查詢相同的內容就不需要再勞煩資料庫了

這個要做的事情比較多,幸好 Laravel 已經幫我們處理了大部分的工作,欲知後事的詳細做法,請參考我的這篇文章

10分鐘內學會快取機制


啟動 LiteSpeed Cache

LiteSpeed Cache 是伺服器層級的快取,當用戶訪問相同的請求網址時,伺服器甚至不需要動用你的應用,就直接把快取的內容往回丟,可節省 CPU 的效能

Untitled

我所使用的 A2Hosting Shared Hosting 是 Turbo 等級,就有提供這個功能,啟動方式如下:

Step 1. 安裝套件

composer require litespeed/lscache-laravel

Step 2.修改 .htaccess

加入以下程式設定

//public\.htaccess

<IfModule LiteSpeed>
   CacheLookup on
</IfModule>

Step 3.檢查是否生效

開啟 Chrome 的開發者工具,切換到 Network 頁籤去查詢 HTML 的標頭是否有如下標記

Untitled


將靜態素材改用 cookieless Domain

要知道所有進入應用的請求都需要經過中介層的處理,哪怕是圖片的下載也是一樣,導致會被加上 cookie 標頭,而對於靜態素材來說,這些內容是沒有必要的。透過將這些素材的網址改用其他非應用網域就能避免被加上 cookie

實現的方法有好幾個,一種是直接使用其他服務器的圖池服務,比如 Amazon 的 S3 或者是 imgur ,如果仍必須用自己的伺服器的話,第二種作法是改用子網域,比如 static.example.com

第一種說法沒啥好談的,只要懂的如何使用圖池服務就行,下面我就來說說如何使用 A2Hosting 來建立 cookieless Doamin

Step 1.先建立子網域

我的目的是希望建立一個名為 static 的子網域,並將其 Document Root 指向到同一專案的 storage/app/public 資料夾

Untitled

以下是我的網域 Document Root 規劃:

example.com | example.com/public

static.example.com | example.com/storage/app/public

Step 2(非必須).修改 Cloudflare

如果你有用 Cloudflare 來管理網域,那你還需要去將 static 這個名稱指向到 A2hosting 的服務器 IP,作法是管理該網域的 DNS 功能,加入 A 類型的資料如下:

Untitled

Step 3.將靜態素材的網域改用子網域

<img class="img-fullwidth" data-original="{{ $item->getModel()->getModel()->getStaticPicUrl() }}" alt="{{ $item->title }}">

參考資料

Serve static content from a cookieless domain in Laravel 5.5


分享這篇文章:

訂閱電子報,索取 Laravel 學習手冊

價值超過 3000 元,包含常用 Laravel 語法與指令!

一小時免費求職講座

3個應徵軟體工程師前該知道的秘訣

取得免費課程連結

Laravel 百萬年薪特訓營

從最基礎的 PHP 語法開始,包含所有你該知道的網頁基礎知識,連同 Laravel 從零開始一直到實戰,最後還將告訴你如何找好工作,讓你及早擁有百萬年薪