next/image をソースコードから理解したい。(クライアントサイド編)
Imageコンポーネントはここで定義されている
基本的なユースケースとレンダリングされるHTMLを見る
sizesを指定しないとき
<Image
alt=""
width={300}
height={400}
src={
"https://picsum.photos/1000/1000"
}
/>
<img
alt=""
loading="lazy"
width="300"
height="400"
decoding="async"
data-nimg="1"
style="color: transparent"
srcset="
/_next/image?url=https%3A%2F%2Fpicsum.photos%2F1000%2F1000&w=384&q=75 1x,
/_next/image?url=https%3A%2F%2Fpicsum.photos%2F1000%2F1000&w=640&q=75 2x
"
src="/_next/image?url=https%3A%2F%2Fpicsum.photos%2F1000%2F1000&w=640&q=75"
/>
sizesを指定するとき
<Image
alt=""
width={300}
height={400}
sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw"
src={
"https://picsum.photos/1000/1000"
}
/>
<img
alt=""
loading="lazy"
width="300"
height="400"
decoding="async"
data-nimg="1"
sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw"
srcset="
/_next/image?url=https%3A%2F%2Fpicsum.photos%2F1000%2F1000&w=256&q=75 256w,
/_next/image?url=https%3A%2F%2Fpicsum.photos%2F1000%2F1000&w=384&q=75 384w,
/_next/image?url=https%3A%2F%2Fpicsum.photos%2F1000%2F1000&w=640&q=75 640w,
/_next/image?url=https%3A%2F%2Fpicsum.photos%2F1000%2F1000&w=750&q=75 750w,
/_next/image?url=https%3A%2F%2Fpicsum.photos%2F1000%2F1000&w=828&q=75 828w,
/_next/image?url=https%3A%2F%2Fpicsum.photos%2F1000%2F1000&w=1080&q=75 1080w,
/_next/image?url=https%3A%2F%2Fpicsum.photos%2F1000%2F1000&w=1200&q=75 1200w,
/_next/image?url=https%3A%2F%2Fpicsum.photos%2F1000%2F1000&w=1920&q=75 1920w,
/_next/image?url=https%3A%2F%2Fpicsum.photos%2F1000%2F1000&w=2048&q=75 2048w,
/_next/image?url=https%3A%2F%2Fpicsum.photos%2F1000%2F1000&w=3840&q=75 3840w
"
src="/_next/image?url=https%3A%2F%2Fpicsum.photos%2F1000%2F1000&w=3840&q=75"
style="color: transparent"
/>
srcsetから理解していく
生成しているところ。
(余談) ブラウザごとのsrcsetの挙動
// It's intended to keep `src` the last attribute because React updates
// attributes in order. If we keep `src` the first one, Safari will
// immediately start to fetch `src`, before `sizes` and `srcSet` are even
// updated by React. That causes multiple unnecessary requests if `srcSet`
// and `sizes` are defined.
// This bug cannot be reproduced in Chrome or Firefox.
GPTによる翻訳
srcを最後の属性に保つのが意図されています。なぜなら、Reactは属性を順番に更新するからです。もしsrcを最初の属性に保持すると、SafariはsrcSetとsizesがReactによって更新される前に即座にsrcを取得し始めます。これにより、srcSetとsizesが定義されている場合に複数の不必要なリクエストが発生します。このバグはChromeやFirefoxでは再現できません。
初めて知った...
loader
とgetWidths
を見てみよう
ドキュメント)
loader (
Imageコンポーネントに指定したloaderあるいはdefaultLoader
が使われる。
src, width, quality
を渡すと画像urlを返す関数
(余談)loaderの読み込みについて
defaultLoader
はビルド時にWebpackが置き換えるらしい。
確かに、next.config.loaderFile に置き換えられていた
getWidths
deviceSizes, allSizes
allSizes
はnext.config.jsからとってきてるっぽい。
allSizes
はallSizesとimageSizesから作られる。
sizesの指定がないとき
width
とwidth
の2倍より大きいものを1つずつallSizesから選択している。
※ 条件に合うものがないときはallSizesから最大のものが選ばれる
const deviceSizes = [640, 750, 828, 1080, 1200, 1920, 2048, 3840]
const imageSizes = [16, 32, 48, 64, 96, 128, 256, 384]
const allSizes = [...deviceSizes, ...imageSizes].sort((a, b) => a - b) // [16, 32, 48, 64, 96, 128, 256, 384, 640, 750, 828, 1080, 1200, 1920, 2048, 3840]
const config = { allSizes, deviceSizes }
var width = 500
getWidths(config, width) // => {width: [640, 1080], kind: 'x'}
var width = 5000
getWidths(config, width) // => {width: [3840], kind: 'x'}
sizesの指定があるとき
sizesって?
(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw
のとき
- ウィンドウサイズが768ピクセル以下の場合、画像の幅はビューポートの100%に設定される。
- ウィンドウサイズが768ピクセルより大きく、1200ピクセル以下の場合、画像の幅はビューポートの50%に設定される。
- ウィンドウサイズが1200ピクセルより大きい場合、画像の幅はビューポートの幅の33%に設定される。
deviceSizesとsizesから、最小の画像幅のピクセルサイズを計算する。
今回の例だとsizes
の最小は33vw
。deviceSizes
の最小は640
なので、
640 x 0.33
で211px
があり得る最小の画像幅になる。
戻り値はallSizesからそれより大きいものがすべて選択される。
const sizes = '(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw'
getWidths(config, 500, sizes) // => {widths: [256,384,640,750,828,1080,1200,1920,2048,3840], kind:"w"}
priorityを理解する
<link
rel="preload"
as="image"
imagesrcset="/_next/image?url=https%3A%2F%2Fpicsum.photos%2F1000%2F1000&w=384&q=75 1x, /_next/image?url=https%3A%2F%2Fpicsum.photos%2F1000%2F1000&w=640&q=75 2x"
fetchpriority="high"
/>
<!-- 中略 -->
<img
alt=""
fetchpriority="high"
width="300"
height="400"
decoding="async"
data-nimg="1"
style="color: transparent"
srcset="
/_next/image?url=https%3A%2F%2Fpicsum.photos%2F1000%2F1000&w=384&q=75 1x,
/_next/image?url=https%3A%2F%2Fpicsum.photos%2F1000%2F1000&w=640&q=75 2x
"
src="/_next/image?url=https%3A%2F%2Fpicsum.photos%2F1000%2F1000&w=640&q=75"
/>
sizesの指定があったとき
<link
rel="preload"
as="image"
fetchpriority="high"
imagesrcset="/_next/image?url=https%3A%2F%2Fpicsum.photos%2F1000%2F1000&w=256&q=75 256w, /_next/image?url=https%3A%2F%2Fpicsum.photos%2F1000%2F1000&w=384&q=75 384w, /_next/image?url=https%3A%2F%2Fpicsum.photos%2F1000%2F1000&w=640&q=75 640w, /_next/image?url=https%3A%2F%2Fpicsum.photos%2F1000%2F1000&w=750&q=75 750w, /_next/image?url=https%3A%2F%2Fpicsum.photos%2F1000%2F1000&w=828&q=75 828w, /_next/image?url=https%3A%2F%2Fpicsum.photos%2F1000%2F1000&w=1080&q=75 1080w, /_next/image?url=https%3A%2F%2Fpicsum.photos%2F1000%2F1000&w=1200&q=75 1200w, /_next/image?url=https%3A%2F%2Fpicsum.photos%2F1000%2F1000&w=1920&q=75 1920w, /_next/image?url=https%3A%2F%2Fpicsum.photos%2F1000%2F1000&w=2048&q=75 2048w, /_next/image?url=https%3A%2F%2Fpicsum.photos%2F1000%2F1000&w=3840&q=75 3840w"
imagesizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw"
/>
<!-- 中略 -->
<img
alt=""
fetchpriority="high"
width="300"
height="400"
decoding="async"
data-nimg="1"
style="color: transparent"
srcset="
/_next/image?url=https%3A%2F%2Fpicsum.photos%2F1000%2F1000&w=256&q=75 256w,
/_next/image?url=https%3A%2F%2Fpicsum.photos%2F1000%2F1000&w=384&q=75 384w,
/_next/image?url=https%3A%2F%2Fpicsum.photos%2F1000%2F1000&w=640&q=75 640w,
/_next/image?url=https%3A%2F%2Fpicsum.photos%2F1000%2F1000&w=750&q=75 750w,
/_next/image?url=https%3A%2F%2Fpicsum.photos%2F1000%2F1000&w=828&q=75 828w,
/_next/image?url=https%3A%2F%2Fpicsum.photos%2F1000%2F1000&w=1080&q=75 1080w,
/_next/image?url=https%3A%2F%2Fpicsum.photos%2F1000%2F1000&w=1200&q=75 1200w,
/_next/image?url=https%3A%2F%2Fpicsum.photos%2F1000%2F1000&w=1920&q=75 1920w,
/_next/image?url=https%3A%2F%2Fpicsum.photos%2F1000%2F1000&w=2048&q=75 2048w,
/_next/image?url=https%3A%2F%2Fpicsum.photos%2F1000%2F1000&w=3840&q=75 3840w
"
src="/_next/image?url=https%3A%2F%2Fpicsum.photos%2F1000%2F1000&w=3840&q=75"
sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw"
/>
ソースコードはここらへん
decodingは常に"async"なんだなぁ。
srcsetを使う場合は、linkタグにhrefを使わないほうがいいらしい
知りたいことわかったので、次はサーバーサイドを見てみる。