はじめに
AstroにはContents CollectionというMarkdownやMDXファイルをコレクションとして管理し、CMSのように扱うことができる非常に便利な機能があります。しかし、デフォルトでは記事中の画像の最適化があまり積極的に行われないため、画像の読み込みに時間がかかってしまいます。
そこで、今回は記事内の画像を最適化する方法をいくつか紹介します。
デフォルトの挙動
まずはデフォルトではどのように画像が表示されるのかを見てみましょう。記事中に
のように画像を配置した場合、デフォルトではこのようなHTMLが生成されます。
<img src="/_astro/image.xxx.webp" alt="alt" loading="lazy" decoding="async" fetchpriority="auto" width="1920" height="1080"/>
<img>
タグを見てみると、画像がWebPに変換され、画像サイズや遅延読み込みなどの属性が設定されていることがわかります。
このままでもWebP変換といった最低限の最適化が行われますが、画像サイズは元画像のままなので、元画像が非常に大きなサイズの場合は、結局画像の読み込みに時間がかかってしまいます。また、Markdownの記法では画像のサイズを指定することができず、通常のHTML記法ではローカル画像の参照ができません。(/public
フォルダは使用できますが、今度は最適化が行われなくなります。)
astro.config.js
でレスポンシブ画像の設定を行う
astro.config.js
にはimage
というオプションがあり、画像最適化の細かい設定やデフォルトの挙動を設定することができます。
そしてその中にあるlayout
オプションを設定することで、画像のレスポンシブ対応を行うことができます。デフォルトではundefined
となっているため、レスポンシブ対応が行われません。とりあえずレスポンシブにしたい場合は"constrained"
に設定しておくとよいです。その他の設定はドキュメントを参照してください。
export default defineConfig({ // ... image: { layout: "constrained", }, // ...});
このオプションを設定した場合、生成されるHTMLはこのようになります。
<img src="/_astro/image.xxx.webp" srcset=" /_astro/image.xxx.webp 640w, /_astro/image.xxx.webp 750w, /_astro/image.xxx.webp 828w, /_astro/image.xxx.webp 1080w, /_astro/image.xxx.webp 1280w, /_astro/image.xxx.webp 1668w, /_astro/image.xxx.webp 1920w " alt="alt" loading="lazy" decoding="async" fetchpriority="auto" sizes="(min-width: 1920px) 1920px, 100vw" style="--fit: cover; --pos: center;" data-astro-image="constrained" width="1920" height="1080"/>
先程の<img>
タグにあった属性に加えて、srcset
やsizes
属性などが設定されていることが分かります。これらの属性によって、デバイスのビューポートに応じて最適なサイズの画像が読み込まれるようになります。これらの属性の詳細に関してはICS MEDIAの記事が参考になるので、そちらをご覧ください。


MDXのカスタムコンポーネントを使用する
astro.config.js
を使用した画像のレスポンシブ化はお手軽で便利ですが、一括設定のみで記事以外の画像もレスポンシブ対応になってしまいますし、srcset
やsizes
属性の調整が難しいです。
記事内の画像のみをレスポンシブ対応にしたい場合はMDXのカスタムコンポーネント機能を利用するのが良いでしょう。
MDXの場合、<Content />
コンポーネントのcomponents
に上書きしたいコンポーネントを設定することができます。残念ながらMarkdownの場合はコンポーネントの上書きができないため、astro.config.js
の設定か、.md
を.mdx
に書き換える必要があります。
まずは、上書きするためのImg.astro
を作成します。これは実際にこのブログのソースコードから抜粋したものです。
---import type { ImageMetadata } from "astro";import { Image } from "astro:assets";
interface Props { src: ImageMetadata | string; alt: string; title?: string;}
const { src, alt, title } = Astro.props;---
{ typeof src !== "string" ? ( <Image src={src} widths={[324, 690, 830, 830 * 2]} sizes="(max-width: 928px) 100vw, 830px" alt={alt} title={title} /> ) : ( <Image inferSize src={src} alt={alt} title={title} /> )}
このブログの場合、記事全体の幅は830px以上になることはないため、sizes
で830pxを最大幅としています。画面幅が928px
以下の場合は記事の幅が画面幅に合わせて縮むため、100vw
としています。
widths
は高dpiデバイスでの表示も考え、830pxの2倍までのサイズを指定しています。
なお、リモート画像の場合は<Image />
にinferSize
を指定する必要があるため、typeof src !== "string"
で条件分岐を行っています。リモート画像の場合は画像の最適化は行われませんが、生成される<img>
タグにwidth
とheight
属性が設定されるため、loading="lazy"
が効果的に機能するようになり、CLSを抑えることもできます。
あとは<Content />
コンポーネントのcomponents
に上書きしたいコンポーネントを設定するだけです。
---import { render, getEntry } from "astro:content";
import Img from "./Img.astro";
const post = await getEntry("posts", "id");const { Content } = await render(post);---
<Content components={{ img: Img }} />
これによって、記事内の画像表示にImg.astro
が使用されるようになります。
まとめ
Astroのコンテンツコレクションは便利ですが、デフォルトでは画像の最適化があまり行われないため、パフォーマンスが悪くなる可能性があります。今回紹介したような方法で記事内の画像を最適化することで、パフォーマンスを向上させることができます。
書いた人