
画像はWebサイト・Webページの構成要素として非常に重要な存在です。また、ページのデータ量の中でも、大きな割合を占めます。
そのため、デバイスに対する画像の最適化は必須であり、現状であれば、解像度&画像フォーマットの両方を考慮した以下のようなレスポンシブイメージのコードが必要です。
<picture> <source type="image/webp" srcset=" image/lemons-w400.webp 400w, image/lemons-w768.webp 768w ... " sizes="100vw" /> <img srcset=" image/lemons-w400.jpg 400w, image/lemons-w768.jpg 768w ... " sizes="100vw" src="image/lemons.jpg" alt="Lemons" loading="lazy" decoding="async" width="1920" height="1258" /></picture>
とはいうものの、このコードを手動で用意するというのは現実的ではありません。フレームワークやCMSを利用するというのが、当然の流れでしょう。
そこで、フレームワークやCMSの対応がどうなっているのかを確認してみました。
Gatsby.js
画像最適化といえば、Gatsby.jsの Gatsby Image でしょう。CMSやフレームワークで、画像の最適化を重要視している自分にとっては、依然として基準となる存在です。
Gatsby Imageでは、以下のようなコードが出力されます。
<div data-gatsby-image-wrapper="" class="gatsby-image-wrapper gatsby-image-wrapper-constrained"> <div style="max-width: 1920px; display: block"> <img alt="" role="presentation" aria-hidden="true" src="data:image/svg+xml;charset=utf-8,%3Csvg height='1440' width='1920' xmlns='http://www.w3.org/2000/svg' version='1.1'%3E%3C/svg%3E" style="max-width: 100%; display: block; position: static" /> </div> <div aria-hidden="true" data-placeholder-image="" style=" opacity: 0; transition: opacity 500ms linear; background-color: #b85808; position: absolute; top: 0; left: 0; bottom: 0; right: 0; object-fit: cover; " ></div> <picture ><source type="image/webp" srcset=" /static/32b64b9034cf3d23ef74966b95004931/a1501/lemons.webp 480w, /static/32b64b9034cf3d23ef74966b95004931/55375/lemons.webp 960w, /static/32b64b9034cf3d23ef74966b95004931/e4c47/lemons.webp 1920w " sizes="(min-width: 1920px) 1920px, 100vw" /> <img width="1920" height="1440" data-main-image="" style="object-fit: cover; opacity: 1" sizes="(min-width: 1920px) 1920px, 100vw" decoding="async" loading="lazy" src="/static/32b64b9034cf3d23ef74966b95004931/3832f/lemons.jpg" srcset=" /static/32b64b9034cf3d23ef74966b95004931/fdd27/lemons.jpg 480w, /static/32b64b9034cf3d23ef74966b95004931/93fdb/lemons.jpg 960w, /static/32b64b9034cf3d23ef74966b95004931/3832f/lemons.jpg 1920w " alt="Lemons" /></picture ><noscript ><picture ><source type="image/webp" srcset=" /static/32b64b9034cf3d23ef74966b95004931/a1501/lemons.webp 480w, /static/32b64b9034cf3d23ef74966b95004931/55375/lemons.webp 960w, /static/32b64b9034cf3d23ef74966b95004931/e4c47/lemons.webp 1920w " sizes="(min-width: 1920px) 1920px, 100vw" /> <img width="1920" height="1440" data-main-image="" style="object-fit: cover; opacity: 1" sizes="(min-width: 1920px) 1920px, 100vw" decoding="async" loading="lazy" src="/static/32b64b9034cf3d23ef74966b95004931/3832f/lemons.jpg" srcset=" /static/32b64b9034cf3d23ef74966b95004931/fdd27/lemons.jpg 480w, /static/32b64b9034cf3d23ef74966b95004931/93fdb/lemons.jpg 960w, /static/32b64b9034cf3d23ef74966b95004931/3832f/lemons.jpg 1920w " alt="Lemons" /></picture ></noscript></div>
理想的なレスポンシブイメージのコードに加えて、プレースホルダ、CLS(Cumulative Layout Shift: レイアウトシフト)対策を見える形で盛り込んだ構成になっています。そのため、CSSによるカスタマイズも簡単です。
CLS対策には、パディングを利用した古いブラウザでも効果のある手法を使っているため、<div>にラップされたものになっています。
<image>~</image>でシンプルに出力されることを期待していると、これほど膨大なコードが出てくるのでちょっと面食らうかもしれませんが、現状のWebで要求されるものをきちんと揃えるとこうなるという、良い見本でもあります。
レスポンシブイメージを構成する、各URLを取得するためのHelper functionsも用意されており、画像を表示するためのコンポーネントを独自に再構成することもできるあたりは、よく考えられていると思います。
Next.js
Next.jsでは、画像最適化のためのコンポーネントとして next/imageが用意されており、次のようなコードを出力します。
<span style=" box-sizing: border-box; display: block; overflow: hidden; width: initial; height: initial; background: none; opacity: 1; border: 0; margin: 0; padding: 0; position: relative; " ><span style=" box-sizing: border-box; display: block; width: initial; height: initial; background: none; opacity: 1; border: 0; margin: 0; padding: 0; padding-top: 75%; " ></span ><img alt="Lemons" src="/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Flemons.e2b3a5a8.jpg&w=3840&q=75" decoding="async" data-nimg="responsive" style=" position: absolute; inset: 0px; box-sizing: border-box; padding: 0px; border: none; margin: auto; display: block; width: 0px; height: 0px; min-width: 100%; max-width: 100%; min-height: 100%; max-height: 100%; " sizes="100vw" srcset=" /_next/image?url=%2F_next%2Fstatic%2Fmedia%2Flemons.e2b3a5a8.jpg&w=640&q=75 640w, /_next/image?url=%2F_next%2Fstatic%2Fmedia%2Flemons.e2b3a5a8.jpg&w=750&q=75 750w, /_next/image?url=%2F_next%2Fstatic%2Fmedia%2Flemons.e2b3a5a8.jpg&w=828&q=75 828w, /_next/image?url=%2F_next%2Fstatic%2Fmedia%2Flemons.e2b3a5a8.jpg&w=1080&q=75 1080w, /_next/image?url=%2F_next%2Fstatic%2Fmedia%2Flemons.e2b3a5a8.jpg&w=1200&q=75 1200w, /_next/image?url=%2F_next%2Fstatic%2Fmedia%2Flemons.e2b3a5a8.jpg&w=1920&q=75 1920w, /_next/image?url=%2F_next%2Fstatic%2Fmedia%2Flemons.e2b3a5a8.jpg&w=2048&q=75 2048w, /_next/image?url=%2F_next%2Fstatic%2Fmedia%2Flemons.e2b3a5a8.jpg&w=3840&q=75 3840w " /><noscript ><img alt="Lemons" sizes="100vw" srcset=" /_next/image?url=%2F_next%2Fstatic%2Fmedia%2Flemons.e2b3a5a8.jpg&w=640&q=75 640w, /_next/image?url=%2F_next%2Fstatic%2Fmedia%2Flemons.e2b3a5a8.jpg&w=750&q=75 750w, /_next/image?url=%2F_next%2Fstatic%2Fmedia%2Flemons.e2b3a5a8.jpg&w=828&q=75 828w, /_next/image?url=%2F_next%2Fstatic%2Fmedia%2Flemons.e2b3a5a8.jpg&w=1080&q=75 1080w, /_next/image?url=%2F_next%2Fstatic%2Fmedia%2Flemons.e2b3a5a8.jpg&w=1200&q=75 1200w, /_next/image?url=%2F_next%2Fstatic%2Fmedia%2Flemons.e2b3a5a8.jpg&w=1920&q=75 1920w, /_next/image?url=%2F_next%2Fstatic%2Fmedia%2Flemons.e2b3a5a8.jpg&w=2048&q=75 2048w, /_next/image?url=%2F_next%2Fstatic%2Fmedia%2Flemons.e2b3a5a8.jpg&w=3840&q=75 3840w " src="/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Flemons.e2b3a5a8.jpg&w=3840&q=75" decoding="async" data-nimg="responsive" style=" position: absolute; top: 0; left: 0; bottom: 0; right: 0; box-sizing: border-box; padding: 0; border: none; margin: auto; display: block; width: 0; height: 0; min-width: 100%; max-width: 100%; min-height: 100%; max-height: 100%; " loading="lazy" /></noscript></span>
基本的な構成は、Gatsby Imageと同様です。<span>でラップしているのも、CLS対策でパディングを利用するためのものです。プレースホルダの処理も行われますが、このコードの中にはありません。
ただし、画像フォーマットの最適化を画像処理APIにまかせているため、そのためのコード(<picture>や<source>)が必要なく、非常にスッキリしたものになっています。SSRを起点としたNext.jsならではだと思います。
そして、これをさらに進めたのが次世代のnext/imageになるとされている、next/future/imageです。
next/future/imageでは以下のようなコードになります。
<img alt="Lemons" sizes="100vw" srcset=" /_next/image?url=%2F_next%2Fstatic%2Fmedia%2Flemons.930bfea0.jpg&w=640&q=75 640w, /_next/image?url=%2F_next%2Fstatic%2Fmedia%2Flemons.930bfea0.jpg&w=750&q=75 750w, /_next/image?url=%2F_next%2Fstatic%2Fmedia%2Flemons.930bfea0.jpg&w=828&q=75 828w, /_next/image?url=%2F_next%2Fstatic%2Fmedia%2Flemons.930bfea0.jpg&w=1080&q=75 1080w, /_next/image?url=%2F_next%2Fstatic%2Fmedia%2Flemons.930bfea0.jpg&w=1200&q=75 1200w, /_next/image?url=%2F_next%2Fstatic%2Fmedia%2Flemons.930bfea0.jpg&w=1920&q=75 1920w, /_next/image?url=%2F_next%2Fstatic%2Fmedia%2Flemons.930bfea0.jpg&w=2048&q=75 2048w, /_next/image?url=%2F_next%2Fstatic%2Fmedia%2Flemons.930bfea0.jpg&w=3840&q=75 3840w " src="/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Flemons.930bfea0.jpg&w=3840&q=75" width="1920" height="760" decoding="async" data-nimg="future" loading="lazy" style="color: transparent; max-width: 100%; height: auto"/>
画像処理APIが存在しているおかげで、<img>のみのシンプルな構成になります。
CLS対策は省略されているわけではなく、width & height属性を元に、ブラウザを通してAspect ratio(縦横比)が指定されています。そのため、古いブラウザではCLSが発生することになります。


IEへの対応を必要としなくなった現状であれば、古いSafariをどこまで気にするかの判断だけです。そうしたSafariを無視できるようになったタイミングで、こちらへ切り替わるのでしょうか。
しかし、画像処理APIを前提としたシンプルなコードは、next exportを利用した静的サイト生成の場合には外部に画像処理APIを必要とするため、ちょっと面倒です。
12.3からunoptimized: trueという、画像の最適化をオフにするという選択肢もできましたが…
たとえば、microCMSであれば外部の画像処理APIとしてimgixが使えますが、next exportのためにコードに手を加えることを考えると、react-imgixといった選択肢も出てきます。
また、自前で外部の画像処理APIを用意するとなると、AWSに用意するterraform-aws-next-js-image-optimizationなどを利用する感じでしょうか。
画像処理APIを必要としないNext Export Optimize Imagesという選択肢も登場しています。シンプルで非常に便利なのですが、Next.jsが出力するコードの構造から、画像の最適化に関してはフォーマットが決め打ちになるのがちょっと残念… と思っていたんですが、WordPressが6.1でのWebP対応を先送りにした流れを確認していて、これで十分なのでは?と思い始めています。
WordPress
WordPressは、標準では以下のようなコードを出力します。解像度に対する最適化のみです。
<figure class="wp-block-image size-full"> <img loading="lazy" width="1920" height="682" src="http://xxx.xxx.xxx.xxx/wp-content/uploads/2022/08/cat.jpg" alt="" class="wp-image-23" srcset=" http://xxx.xxx.xxx.xxx/wp-content/uploads/2022/08/cat.jpg 1920w, http://xxx.xxx.xxx.xxx/wp-content/uploads/2022/08/cat-300x107.jpg 300w, http://xxx.xxx.xxx.xxx/wp-content/uploads/2022/08/cat-1024x364.jpg 1024w, http://xxx.xxx.xxx.xxx/wp-content/uploads/2022/08/cat-768x273.jpg 768w, http://xxx.xxx.xxx.xxx/wp-content/uploads/2022/08/cat-1536x546.jpg 1536w, http://xxx.xxx.xxx.xxx/wp-content/uploads/2022/08/cat-1568x557.jpg 1568w " sizes="(max-width: 1920px) 100vw, 1920px" /></figure>
WordPress Performance TeamがリリースしているPerformance Labプラグインを追加することで、以下のようなコードを出力するようになります。
<figure class="wp-block-image size-large"> <img data-dominant-color="ba6c14" data-has-transparency="false" style="--dominant-color: #ba6c14" width="1024" height="768" src="http://xxx.xxx.xxx.xxx/wp-content/uploads/2022/09/lemons-g132a8b895_1920-1024x768-jpg.webp" alt="" class="not-transparent wp-image-109" srcset=" http://xxx.xxx.xxx.xxx/wp-content/uploads/2022/09/lemons-g132a8b895_1920-1024x768-jpg.webp 1024w, http://xxx.xxx.xxx.xxx/wp-content/uploads/2022/09/lemons-g132a8b895_1920-300x225-jpg.webp 300w, http://xxx.xxx.xxx.xxx/wp-content/uploads/2022/09/lemons-g132a8b895_1920-768x576-jpg.webp 768w, http://xxx.xxx.xxx.xxx/wp-content/uploads/2022/09/lemons-g132a8b895_1920-1536x1152.jpg 1536w, http://xxx.xxx.xxx.xxx/wp-content/uploads/2022/09/lemons-g132a8b895_1920-1568x1176.jpg 1568w, http://xxx.xxx.xxx.xxx/wp-content/uploads/2022/09/lemons-g132a8b895_1920-jpg.webp 1920w " sizes="(max-width: 1024px) 100vw, 1024px" /></figure>
Webpを使った、解像度の最適化のみのレスポンシブイメージのコードを出力します。てっきり、解像度&フォーマットの両対応なコードが出力されるものだと思っていただけに、デフォルトをWebPにしてしまう、この潔さにはちょっとびっくりしました。
しかし、WordPressの現在の考え方からすれば、これは当然の流れなのかもしれません。WordPressはIEのサポートを打ち切ってから、モダンCSSへのリファクタリングがすごい勢いで進んでいます。もちろん、CLSへの対策もwidth & height属性を使ったAspect ratioによるものです。
WebPのみを使ったシンプルなレスポンシブイメージになる?
ここで、各ブラウザのWebPへ対応状況を確認してみます。

これを見る限り、Aspect ratioに対応している環境であれば、WebPも問題ないのです(SafariがmacOSがらみで、ちょっと怪しい部分もありますが)。
であれば、デフォルトの画像フォーマットとしてWebPを選択してしまうというのはありなわけです。
一方で、Adobe PhotoshopがWebPをネイティブサポートしたのが2022年2月にリリースされたPhotoshop23.2からといった状況を考えると、WebPはまだまだ一般的なものとはいえません。
そのため、すぐにデフォルトフォーマットとして扱うというのは、流石に厳しいのかもしれません。
WordPressのコアへの投入は、問題になりそうなSafariを気にしなくて良くなる頃までは待つ感じでしょうか…その頃であれば、WebPがもっと一般的な存在になっていそうですし。
そして、このWordPressが出力するコードを見ていると、Next.jsで静的サイト生成する場合はnext/future/image & Next Export Optimize ImagesでWebP出力で十分なのでは?と思えてくるわけです。
古いSafari(~v14 or v15)を気にする必要がなくなる頃には、WebPももう少し一般的な存在になれるでしょうか。そうなってくれば、レスポンシブイメージは非常にシンプルなところに落ち着きそうな気がします。
静的サイト生成であり、画像まわりも整ってきたということでAstroも試してみたのですが、公式の@astrojs/imageがexperimental扱いなのに加えてastro-imagetoolsの方が好みだったりで色々と迷ったため、今回は取り上げるのを見送りました。