說到在畫面中加入圖片,大家第一個想法都是使用 <img>
標籤,但是在 Next.js 卻另外提供了一個也是用來放上圖片的用法 <Image>
。
今天我們就來認識一下 Next.js 專案中提供的<Image>
,以及 <Image>
與一般我們在使用的 <img>
標籤有什麼差異。
在頁面中放入圖片是一個非常常見的事情,圖片可能造成的問題也非常容易被忽略,因為大多數時候我們只會去注意圖片能不能正常顯示出來。但以使用者而言,除了圖片能否正常顯示出來外,圖片顯示的速度也會對使用者造成負面影響。我想大家應該都有遇過等待網頁中的圖片顯示等到不耐煩的經驗,不過圖片在網頁中,除了有可能因為出現顯示過慢,導致使用者無法快速看到重要資訊外,還有可能產生其他問題。
在正式認識 Next.js 的 <Image>
之前,先來看圖片可能會對頁面或使用者體驗造成什麼樣的問題。
圖片會造成的問題主要是以下這幾個部分:
有鑒於圖片有可能會出現以上這幾個會影響使用者體驗的問題,Next.js 提供了讓圖片能夠避免出現上述問題的 <Image>
。
<Image>
?<Image>
是一個 Next.js 提供可以用來優化圖片的強化版 <img>
元件。不論是 Page Router 還是 App Router 都可以使用 <Image>
,而且在使用方法上,沒有太大的差異。雖然說使用時的寫法是用 <Image>
這個 Next.js 提供的元件,但元件內部其實還是使用 HTML 的 <img>
標籤,只是把相關的優化邏輯則是包裝在 Image 這個元件內。(詳細的元件內容可以查看 Next.js 專案中的這個檔案 /next.js/packages/next/src/client/image-component.tsx
。)
使用 <Image>
時,主要會有以下的優化效果:
<Image>
?接下來我們再來看一下「為什麼使用 <img>
就可以了,Next.js 卻還要另外提供 <Image>
這個用法呢」?
我們先來看一個情境,這個情境是在畫面中放上一個檔案比較大的圖片(圖片大小為 13.9 MB)。這裡會用一般的 <img>
標籤和使用 <Image />
來做比較。在這個比較中,會使用同一張圖,並設定相同的寬高。除此之外,還會把 Throttling 設定為 Fast 4G。
先來看用 <img>
時的情況。
首先可以發現到因為等待的時間有點長,所以畫面會一直空著,等到整個請求都完成,才顯示圖片。
接著從下面這張圖可以知道當我們使用 <img>
的時候,總共花了 17.64 秒才完成整個畫面的請求,以及所有資源壓縮後的總大小約 21.7 MB。
接著再來看使用 <Image>
時的狀況。
從頁面實際呈現的狀況,很明顯可以看得出來當我們使用 <Image>
時,圖片變得比較快就顯示出來,而且也不會等到完整下載後才一次完整地顯示。
看實際數據的話,也可以看到完成整個畫面的請求秒數縮短,且所有資源壓縮後的總大小也大幅減少。
看了這個比較後,應該就不難理解為什麼明明使用 <img>
就好,Next.js 還要增加 <Image>
這個用法。主要就是因為使用 <Image>
時,可以針對圖片渲染的效能進行優化。
接著我們再來看看 Image 元件的使用方式。
使用時,因為需要幫圖片預留位置,避免出現畫面位移的狀況,所以會需要加上寬高,但如果不知道圖片的寬高,也可以透過設定 fill={true}
讓圖片自動套用父層的寬高(但是父層必須使用 position: "relative", "fixed", "absolute")。圖片路徑的部分,則和 img 標籤一樣是透過 src 帶上。
這樣就是最基本的使用方式。
import Image from "next/image";
const ImagePage = () => {
return (
<div>
<h1>ImagePage</h1>
<Image
src="/picture.jpg"
alt="new-picture"
width={600}
height={400}
/>
</div>
)
}
export default ImagePage
除了這樣的基本使用方式外,<Image>
還有其他 props 可以拿來使用。
使用 image 元件的圖片預設都會使用 lazy loading 的方式來處理圖片,如果想要關閉這個設定,可以使用 priority 這個 prop,這樣圖片就會在一開始就被加載進來。
import Image from "next/image";
import beautiful from './beautiful.jpg'
const ImagePage = () => {
return (
<div>
<Image
src={beautiful}
alt="Next.js logo"
width={1122}
height={800}
priority
/>
</div>
)
}
export default ImagePage
也可以使用 lazy="eager"
來讓圖片立即載入,改用 lazy="loadgin"
則等於採用了 lazy loading 的方式載入。
import Image from "next/image";
import beautiful from './beautiful.jpg'
const ImagePage = () => {
return (
<div>
<Image
src={beautiful}
alt="Next.js logo"
width={1122}
height={800}
lazy="eager"
/>
</div>
)
}
export default ImagePage
這裡也看一下關閉 lazy loading 和使用預設的 lazy loading 在照片載入時的差異異。
第一張照片因為我有使用 priority,所以它很快就渲染完成了,底下的照片則是因為沒有使用 priority,再加上圖片不在一開始的可視範圍內,所以很明顯地比較晚才完全顯示出來。
可以透過 placeholder 這個 prop 帶上 blur,在圖片還沒載完前先讓圖片區塊以模糊的圖片顯示。
import Image from "next/image";
import beautiful from './beautiful.jpg'
const ImagePage = () => {
return (
<div>
<Image
src={beautiful}
alt="Next.js logo"
width={1122}
height={800}
placeholder="blur"
/>
</div>
)
}
export default ImagePage
使用 Image 元件時,可以發現到圖片預設都會被轉換成 webp 的檔案。
如果希望 Image 元件幫我們優先轉換成其他圖片格式,也可以在 next.config 檔案裡面設定。
import type { NextConfig } from "next";
const nextConfig: NextConfig = {
/* config options here */
images: {
formats: ['image/avif', 'image/webp'],,
},
};
export default nextConfig;
這樣設定後,圖檔就會變成 avif 的格式了。
以上就是幾個我自己覺得比較常用的一些用法。
<Image>
會是所有圖片的優化最佳解?前面我們已經了解到為什麼 Next.js 要另外提供 <Image>
的這個用法,也了解到它與一般的 img 標籤的差異。現在我們再回過頭來思考一個問題,那就是「專案裡面的每一個圖片都有需要使用到 <Image>
嗎?」。
雖然 <Image>
幫我們自動針對圖片做了一些優化,但也因為需要額外多做一些動作來優化圖片,當圖片檔案沒有那麼大,這樣的優化行為,反而會多耗一些效能。
這裡也讓我們看一個小範例,這個例子會分別把一般的 <Img>
和 Next.js 的 <Image>
用在大小比較小的圖片上,我們可以來實際觀察使用檔案大小較小的圖片時,這兩者的差異是什麼。
這裡準備一張大小為 13KB 的圖,個別用 <Image>
元件和 <img>
標籤放在不同的頁面中。
實測結果可以看到使用 Image 的時候,第一次進入頁面,會需要花費 2.37 秒才完成畫面渲染。
使用 <img>
的時候,第一次進入頁面的秒數卻是 1.93 秒。
這個結果很令人驚訝吧!明明前面一開始看的示範,使用 Image 元件的時候,秒數會大幅減少,但是在這個例子中,使用 Image 元件的秒數卻反而稍微比使用 img 標籤多了一點。
這是因為檔案 size 較小的圖片,原本可以優化的程度有限,如果還是使用 Image 元件的話,還是需要讓 Next.js 花費優化的固定成本,去執行 Image 元件裡面處理優化的一些邏輯,整體結果就會變成有使用 Image 元件的時候反而花費比較多時間。所以在 Next.js 中,並不是什麼圖片都使用 Image 元件就能達到優化的效果,如果圖片檔案真的很小,還是使用 Image 元件的話,反而有可能會造成反效果,至於 Image 元素所提供的幫圖片佔一個空位,避免畫面位移的優化部分,如果單純使用 img 標籤時,則可以透過 css 來處理就好。
所以使用 Next.js 提供的 Image 元件並不一定是優化的最佳解。
<Image>
的使用誤區除了在圖片檔案過小時不一定需要使用 Image 元件之外,其實還有其他一些細節,若沒注意反而可能讓 Image 元件帶來反效果。
∙ 首屏圖片沒設定 priority 造成 LCP 變差
由於 Image 元件預設會使用 lazy loading,如果在一開始進入頁面的可視範圍內,有佔畫面比較大範圍的圖片,有可能會造成首屏的載入速度變慢,也就會造成 LCP(Largest Contentful Paint) 的時間變長。所以如果是首屏會顯示的圖片,就建議加上 priority 或 loading="eager",來關閉預設的 lazy loading 設定。
∙ 無腦使用 priority 造成效能變差
雖然說首屏圖片要加上 priority 才會讓整體的畫面載入速度變好,但這並不代表要將頁面中的所有圖片都加上 priority,如果太多圖片都被設定 priority,那反而拖慢 LCP。所以還是要依照實際情況使用 priority 這個設定來關閉 lazy loading,如果是不需要先顯示的圖片,建議還是使用 lazy loading,才不會阻擋真正該顯示出來的內容。
∙ fill 父容器沒固定比例造成 CLS (Cumulative Layout Shift)
雖然 Image 元件可以設定 fill={true}
讓圖片自動套用父層的寬高,但若父層沒有設定固定的寬高比例,圖片在載入前父層仍會是沒有高度的狀態,也就會導致版面位移(CLS)的狀況發生。由於使用 fill 時,父層需要使用 position: relative,因此常見的做法是透過 CSS aspect-ratio 或固定高度來維持比例,以避免圖片載入後才撐開畫面。
Image 元件是 Next.js 提供的一個能夠優化圖片的元件,雖然是一個用來替代 Img 標籤的寫法,但實際上還是使用 Img 這個標籤,只是被 Next.js 包裝成名為 Image 的元件來使用。雖然是 Image 元件是用來優化圖片效能的元件,但是並不是有用有保佑、有用就能達到真正的圖片優化效果,還是必須依照當前的使用狀況去判斷該如何使用,像是要不要使用 priority,或是有沒有確實設定寬高,以及依照圖片大小去判斷是不是需要使用到 Image 這個元件。如果只是覺得有用就好的話,有時候不僅無法對效能有正面效果,可能還有反效果。
官方文件 - Image Optimization
官方文件 - Image Component