AssetsでAstroの画像最適化を試した。

相変わらず体調があんまりよくなくて滅入っています。 それはそれとして、ブログを Astro に移行する際に後回しにした画像最適化に取り組みました。 今まで Markdown の記事では public の画像を直接表示していましたが、 Assets による画像最適化を試すことにします。

Astro の画像最適化

Astro では画像最適化にいくつか選択肢があります。

このブログでは Markdown から![]()記法で画像を表示させたいので、Assets を採用しました。

public からの移行

移行準備

公式ドキュメントに従って作業を行いました。

Assets (Experimental) 🚀 Astro Documentation

デフォルトでは画像変換に Squoosh が使われるのですが、GIF の変換に対応していなかったため sharp を使いました。

Markdown の書き換え

今まではpublicから見た相対パスで画像を指定していましたが、Assets では Markdown ファイルから見たsrc/assetsへの相対パスで画像を指定できるようになります。 run dev せずとも Markdown プレビューで正しい表示が得られるのと、VSCode で記事を書く時にサジェストが効くのでこれはかなり嬉しいです。

CSS の書き換え

HTML に出力される img タグの width と height は元画像と同じ数値が設定されます。 height が大きすぎる画像もあるため、レンダリングされる画像の大きさを制限するためにmax-heightに 500px を指定しました。 これだけだとアスペクト比が無視されるため、object-fit, object-positionでレイアウトシフトを防ぎつつ縮小させています。

← object-fit適用前 | object-fit適用後 →
← object-fit 適用前 | object-fit 適用後 →

Gatsby だとバグって正しく出力できなかったのですが、Astro では期待通りの見た目になりました。

出力結果

画像

Markdown から呼び出した画像は WebP に変換されました。 過去の記事で使用した画像(ジャッシー十代さん)の PNG を確認したところ、ファイルサイズは 114.55KB -> 20.2KB となっておりむちゃくちゃ圧縮されています。 ぱっと見で「かなり画質が劣化してるな~」と感じましたが、実際に元画像と比較すると「やっぱめっちゃ劣化してるな~」という気持ちになりました。

← 元画像 | WebP →
← 元画像 | WebP →

いや、並べてもわからないかもしれない。「元画像を知ってると劣化してるのがはっきりわかる」という感想を伝えたかったのですが、例示した画像が悪かったですね。

スクリーンショット程度であれば問題ないですが、写真やイラストを扱う時は<Image />コンポーネントでqualityformatを指定しないと厳しそうです。 Markdown では<Image />コンポーネントが使用できないので、必要に応じて MDX を採用したいと思います。

img タグ

出力された img タグは以下の通りでした。

<img
  alt="ジャッシー十代ちゃん"
  src="/_image?href=%2F%40fs%2Fhome%2Fsakuramochi%2Fastro-blog%2Fsrc%2Fcontent%2Fposts%2F_images%2F2013%2F03%2Fjujas.png%3ForigWidth%3D500%26origHeight%3D500%26origFormat%3Dpng&amp;f=webp"
  width="500"
  height="500"
  loading="lazy"
  decoding="async"
/>

loading="lazy"decording="async"により画像そのものだけでなく読み込みも最適化されています。

遅延読み込み - ウェブパフォーマンス | MDN

loading 属性 loading 属性を <img> 要素に(または loading 属性を <iframe> 要素に)設定することで、ユーザーが近くにスクロールするまで、画面に表示されている画像や iframe の読み込みを延期するように、ブラウザーに指示することができます。

HTMLImageElement: decoding プロパティ - Web API | MDN

async 画像を非同期でデコードすることで、他のコンテンツ提示の遅延を減らすことができます。

ビルドの所要時間

画像 120 枚程度で 26.81s.でした。(GitHub Actions の Linux 仮想マシン環境下)

解決できなかった箇所

sharp は入力可能な画像サイズにデフォルトで制限があります。最適化の対象にでかい GIF アニメが混ざっており、ビルド時に Input image exceeds pixel limit エラーが出るため sharp の設定を変更したかったのですが、 Assets の API から sharp の API を呼ぶ方法を見つけられませんでした。

仕方ないのででかい GIF アニメのピクセル数を落として対処しました。

所感

Experimental とはいえブログで使うレベルでは全く困らないのと、v3 で大幅に仕様が変わることはないと踏んでそのまま Assets を使い続けることにしました。 使用感もかなり良かったです。

ビルド時間に関して言及すると、ローカルでは勝手にビルド結果をキャッシュしてくれるので全く気にならなかったのですが、 いざ GitHub Actions でビルドすると当然キャッシュなんか存在せず時間がかかったので、次はこのあたりをどうにかしたいです。

(2023/07/21 追記) GitHub Actions でキャッシュをどうするかの問題ですが、sharp のキャッシュは node_modules/.astro/assets に保存されるので、hashFilesnode_modules/.astro/assets/** を指定することで解決しました。 画像が増える度に Actions のキャッシュも増えるので新旧のキャッシュが混在することになりますが、restore-key は最新のキャッシュを復元するため、この運用で問題なさそうです。