Astro

Astroの画像最適化とパフォーマンス改善|astro:assetsとprefetchの実装

Astroの画像最適化とパフォーマンス改善|astro:assetsとprefetchの実装

Astroの画像最適化は組み込みの astro:assets で完結し、追加のプラグインは要りません。ただし <Picture /> のフォールバック画像は既定でPNGのまま残り、実測ではWebPの62倍のファイルを配信していました。この記事では Astro 7.2.2 で実際にビルドして得た出力HTMLとファイルサイズをもとに、画像最適化・遅延読み込み・先読み(prefetch)の実装と、PageSpeed Insightsのスコアをどこまで追うべきかを整理します。

まとめ:Astroの画像最適化とprefetchの要点

  • 画像最適化は astro:assets が標準搭載。旧 @astrojs/image は 0.18.0(2023年8月30日)を最後に npm 上で非推奨
  • <Image />loading="lazy"decoding="async" を既定で出力し、変換先の既定はWebP
  • <Picture /> のフォールバック既定はPNG。元画像がPNGならPNGのまま生成される(800w比較で62倍)
  • ファーストビューの画像は priority 一つで loading="eager"decoding="sync"fetchpriority="high" に切り替わる(Astro 5.10.0以降)
  • レスポンシブな srcset を出すには layout の指定が必要。既定値は none で自動生成なし
  • prefetch の既定戦略は hover。<ClientRouter /> を使う場合は prefetchAll が既定で有効
  • Lighthouse の Performance 配点は TBT 30・LCP 25・CLS 25・FCP 10・SI 10。100点固執より配点の大きい2項目が先

本文の数値は、Astro 7.2.2・sharp 0.35.3・Node v26.5.0(macOS)で、1600×900・1,605,266バイトのPNGを image: { layout: 'constrained', responsiveStyles: true } の設定でビルドした実測値です。再現条件を明示したうえで、各実装を具体的に見ていきます。

astro:assetsによる画像最適化の実装

Astro 3.0 以降、画像最適化はフレームワーク本体の機能です。日本語の解説記事には npx astro add image@astrojs/image を前提にしたものが今も残っていますが、このパッケージは 0.18.0(2023年8月30日)が最終版で、npm には「astro:assets への移行」を促す非推奨メッセージが登録されています。新規に導入する必要はありません。

Imageコンポーネントの出力HTMLと自動付与される属性

---
import { Image } from 'astro:assets';
import hero from '../assets/hero.png';
---
<Image src={hero} alt="hero" width={800} />

この記述を image.layout: 'constrained' の設定でビルドすると、次のHTMLが出力されます(srcset は実際には7本、紙面の都合で幅の値だけを残しています)。

<img src="/_astro/hero.Du9AQ5Sp_Z2vVaao.webp"
  srcset="...640w, ...750w, ...800w, ...828w, ...1080w, ...1280w, ...1600w"
  alt="hero" loading="lazy" decoding="async"
  sizes="(min-width: 800px) 800px, 100vw"
  data-astro-image="constrained" data-astro-image-fit="cover"
  data-astro-image-pos="center" width="800" height="450">

指定したのは srcaltwidth の3つだけです。高さ 450px は元画像の縦横比から自動補完され、loadingdecoding、7段階の srcsetsizes、切り抜き位置を制御する data-astro-image-fitdata-astro-image-pos も自動で付きます。widthheight が必ず入ることが、レイアウトシフト(CLS)対策として効きます。なお alt は省略するとビルドエラーになる必須propです。

Pictureのフォールバックが元形式のまま残る落とし穴

ここが最も見落とされやすい箇所です。Astro 7.2.2 の Picture.astro が定義するフォールバック形式の既定値は defaultFallbackFormat = 'png'。例外は gifsvgjpgjpeg の4形式だけで、これらには元の形式がそのまま使われます。つまり元画像がPNGのとき、<picture> 内の <img> はPNGのまま出力されます

同じ元画像から800w相当の生成物を比べた実測値が下の表です。

形式 800wのサイズ 生成7本の合計
AVIF 8,394バイト 92,861バイト
WebP 11,180バイト 116,058バイト
PNG(既定のフォールバック) 694,330バイト 7,464,215バイト

WebPの11KBに対しPNGは678KB、約62倍です。AVIFとWebPに対応しないブラウザだけが読む画像とはいえ、ビルド成果物としては7.1MBが常に生成され、デプロイ対象に乗り続けます。PNG素材を扱うなら fallbackFormat="webp" を明示してください。WebPは主要ブラウザで長く対応済みで、PNGフォールバックを残す実利はほとんどありません。

<Picture src={hero} formats={['avif', 'webp']} fallbackFormat="webp"
  alt="hero" width={800} />

この指定に変えると、フォールバックの <img>/_astro/hero...webp を指し、PNG7本の生成自体が不要になりました。既定のまま使う判断が残るのは、完全なロスレスが要件の画像だけです。WebPも透過とロスレス圧縮に対応するため、透過の有無は判断基準になりません。

qualityと形式指定による圧縮率の調整

圧縮率を直接動かすpropが quality です。low から max のプリセット、または 0〜100 の数値を渡します。同じ元画像・同じ800wで比較すると、既定の11,180バイトに対し quality={40} では7,636バイトまで下がりました。約32%の削減です。

<Image src={hero} alt="hero" width={800} quality={40} />

形式ごとの既定パラメータは image.service.config でまとめて指定できます。写真主体のサイトなら quality を下げる効果が大きく、図版やスクリーンショット中心なら下げすぎると文字の輪郭が崩れます。数値を1つ決めて全画像に流すのではなく、用途ごとに2〜3段階を使い分けてください。

レスポンシブ画像のlayoutとsrcset自動生成

レスポンシブ画像は Astro 5.0 で実験的機能として入り、5.10.0(2025年6月19日)で安定版になりました。この時に experimental.responsiveImages フラグは削除され、設定名も最終形へ変わっています。ここで押さえるべき前提が2つあります。

1つ目は layout の指定です。既定値は none で、layout を渡さず image.layout も設定しない場合、出力される <img> には srcsetsizes も付きません。実測でも layout="none" の画像は単一のWebPと widthheightloadingdecoding だけの出力でした。2つ目は付随スタイルで、image.responsiveStyles: true を明示しないと object-fit などのCSSが出力されません(既定 false)。srcset の生成自体には不要な設定なので、混同しないでください。

スタイルの出力方式は版ごとに変わっています。Astro 6.0.0 ではビルド時に仮想モジュールから出す方式へ変更され、7.0.1 では生成スタイルが @layer astro.images でラップされました。利用者側のCSSレイヤーより優先度を下げる修正です。独自CSSで画像の見た目を上書きしている場合は、この版差を確認してください。

layout 挙動 主な用途
constrained 指定幅を上限に縮小 本文中の図版
full-width 常に幅100% ヒーロー画像
fixed サイズ固定 アイコン・ロゴ
none(既定) 自動生成なし 自前でsrcsetを組む場合

実測では constrained かつ width={800} の指定に対し、640・750・800・828・1080・1280・1600 の7幅が生成され、sizes="(min-width: 800px) 800px, 100vw" が付きました。断ち落としの基準となる幅を width で決めれば、あとは自動という設計です。幅を自分で制御したい場合は widthssizes を直接渡します。

元画像の準備とpublicフォルダの扱い

公式ドキュメントは「Images stored in the public/ folder are never optimized.」と明記しています。public/ 配下の画像は圧縮も形式変換もされず、レスポンシブ画像の対象にもなりません。最適化させたい画像は src/assets/ に置き、コンポーネントから import してください。ここを取り違えると、いくら設定を足しても出力は変わりません。

元画像を事前に圧縮しておく価値も残ります。今回の検証で使ったグラデーション主体のPNGでは、1280w のPNG再生成物が 1,670,327バイトとなり、元画像の 1,605,266バイトを約4%上回りました。1600w は 1,607,599バイトで元画像とほぼ同値です。階調が連続する画像に限った条件付きの結果ですが、sharp による再エンコードが常に縮小方向へ働くとは限らないことは押さえてください。写真であればJPEG、階調の少ない図版であればPNGと、素材側で形式を選んでおいたほうが結果は安定します。

画像点数が多いサイトでは、ビルドコストが設計上の制約になります。今回は元画像1枚から21ファイル(AVIF・WebP・PNG各7幅)が生成され、画像処理だけで4.4〜5.9秒(同一プロジェクトで2回実行)かかりました。1枚あたり21倍という生成比率は点数に比例して効いてくるため、数千点規模のポータルサイトではまず生成枚数を削る設計から入りますformats をAVIFとWebPの2形式に絞る、widths を実際に使うブレークポイントだけに限定する、更新頻度の低い画像は最適化済みの成果物をリポジトリに置いて public/ から配信する、といった選択肢です。ビルド時最適化にこだわらず、画像だけを画像配信CDNへ逃がして image.service をパススルーに切り替える判断もあります。

遅延読み込みと先読みの使い分け

ファーストビュー画像へのpriority指定

<Image /> の既定出力は loading="lazy" です。画面外の画像には最適ですが、ファーストビューの主役画像に付いたままだとLCPを悪化させます。ブラウザが「後回しでよい画像」と解釈するためです。Astro 5.10.0 で追加された priority propを使えば、属性を1つ書くだけで最適値に切り替わります。

<Image src={hero} alt="hero" width={1200} priority />

実際の出力は loading="eager" decoding="sync" fetchpriority="high" の3属性でした。decodingsync になる点が手書きとの差で、個別に上書きしたい場合は各属性を直接渡せます。ヒーロー画像とメインビジュアルには priority、それ以外は既定のまま、という切り分けが基本です。

ページ遷移を先読みするprefetchの設定

ページ遷移そのものの体感速度は prefetch で改善します。混同しやすいのですが、ここで扱うのは次に開くページのHTMLを取りに行く仕組みで、現在のページ内のリソースを優先取得する <link rel="preload"> とは目的が別です。設定を有効化すると、全ページに先読み用スクリプト(実測2,490バイト)が読み込まれ、リンクに data-astro-prefetch を付けられるようになります。

export default defineConfig({
  prefetch: { prefetchAll: true, defaultStrategy: 'viewport' },
});
戦略 発火タイミング
hover(既定) ホバーまたはフォーカスから80ミリ秒後
tap クリックやタップの直前
viewport リンクが画面内に入って300ミリ秒後
load ページ読み込み後、対象リンクをまとめて

戦略の適用範囲を決めるのは指定方法です。data-astro-prefetch を付けたリンクだけが対象で、prefetchAll: true にすると属性なしの内部リンクも defaultStrategy の戦略で先読みされます。生成スクリプトを読むと、hover は mouseenter から80ミリ秒の遅延を挟んで発火する実装でした。カーソルが通過しただけの誤発火を抑える設計です。tap は touchstartmousedown を拾い、低速回線の判定を無視して即取得します。実装は <link rel="prefetch"> が使えればそれを、使えなければ fetch() を使います。

実務でつまずくのは、先読みが「効かない」ケースです。スクリプトは navigator.connection のデータセーバー設定と 2g 判定を見ており、該当する環境ではtap相当まで抑制されます。開発機で確認できても実ユーザーには届いていない、という乖離の原因がここです。<ClientRouter />(ビュートランジション)との併用も注意点で、この場合は prefetchAll が既定で有効になり全リンクが対象になります。リンク数の多い一覧ページでは通信量が増えるため、defaultStrategytap に寄せるのが無難です。なお prefetch() 関数の eagerness オプションは、experimental.clientPrerender を有効にした場合にかぎり機能します(5.6.0で追加、7.2.2時点でも実験的機能)。フラグなしでは値が無視されるため、指定しても挙動は変わりません。

PageSpeed Insightsで100点を追うべきかの判断

ここは立場を明確にします。100点を目標にするのは、LCPとTBTを直し終えた後にしてください。根拠は配点です。Lighthouse 13.4.1 の既定設定ファイルでは、Performance カテゴリの重みが TBT 30・LCP 25・CLS 25・FCP 10・SI 10 と定義されています。合計100のうち55点をTBTとLCPが占めており、AstroがJavaScriptを既定で出力しない設計はTBTに直接効きます。

同じ設定ファイルで interaction-to-next-paint の重みは 0 です。INPは実ユーザーの操作がないと測れないため、ラボ計測であるLighthouseではスコアに算入されません。つまりラボで100点でも、実ユーザーデータ(CrUX)のINPが基準を外していることは普通に起こります。判定に使うべきはフィールドデータで、基準はLCP 2.5秒以内・INP 200ミリ秒以内・CLS 0.1以内です。指標の定義と改善手順はコアウェブバイタルとは?3指標(LCP・INP・CLS)の基準と改善方法【2026年版】、配点の内訳はLighthouseスコアの見方と改善方法|Best Practices・SEOの配点と5カテゴリで詳しく扱っています。

そもそもGoogleは、ページエクスペリエンスについて「There is no single signal.」(単一のシグナルは存在しない)と述べ、さらに「Google Search always seeks to show the most relevant content, even if the page experience is sub-par.」(ページエクスペリエンスが不十分でも、最も関連性の高いコンテンツを表示しようとする)と明記しています。スコアの最後の数点を詰める工数は、内容の充実へ回すのが合理的です。TTFBやSpeed Indexなど周辺指標の位置づけはWebパフォーマンス指標の一覧と使い分け|TTFB・FCP・TBT・Speed Indexの基準を参照してください。

よくある質問

Astroで画像最適化にプラグインは必要ですか?

不要です。astro:assets が本体に組み込まれており、npx astro add image の実行も要りません。@astrojs/image は 0.18.0(2023年8月30日)で更新が止まり、npm上でも非推奨として登録されています。既存プロジェクトに残っている場合は削除して astro:assets へ移行してください。

publicフォルダの画像も最適化されますか?

されません。公式ドキュメントが「never optimized」と明記しており、圧縮・形式変換・レスポンシブ対応のいずれも適用されません。ファビコンなどURLを固定したい画像だけを public/ に置き、記事内の画像は src/assets/ に置くのが基本方針です。

ImageとPictureはどちらを使うべきですか?

単一形式でよければ <Image /> で十分です。既定でWebPへ変換されます。AVIFも配信して転送量をさらに削りたい場合に <Picture /> を使い、そのときは fallbackFormat の指定を忘れないでください。既定のPNGフォールバックが残ると、実測で62倍のファイルが生成対象になります。

prefetchの既定の戦略は何ですか?

data-astro-prefetch を属性値なしで付けた場合は hover です。ホバーやフォーカスから80ミリ秒後に先読みが走ります。defaultStrategy で既定を変更でき、<ClientRouter /> を使っている場合は prefetchAll が既定で有効になる点が例外です。

Astro 7へ上げるとき画像まわりで注意する点はありますか?

画像APIそのものに破壊的変更はありません。ただし 7.0.0(2026年6月22日)でコンパイラがRust製に置き換わり、閉じていないHTMLタグがエラーになるなど構文チェックが厳格化しました。compressHTML の既定値も 'jsx' へ変わり、インライン要素間の空白の扱いが変化します。Markdownの既定処理系も Sätteri に切り替わったため、remark/rehypeプラグインを使っているなら @astrojs/markdown-remark の明示的な導入が必要です。

関連記事

資料請求

RELATED POSTS 関連記事