Astro

Astroの画像最適化とpreload・prefetch|astro:assetsの実装【Astro 7.4】

Astroの画像最適化とpreload・prefetch|astro:assetsの実装【Astro 7.4】

Astroでファーストビューの画像やフォントを先に読ませる公式の手段は3つあります。取得優先度を上げる<Image>のpriority、getImage()の結果で<link rel="preload">を書く方法、Fonts APIの<Font preload>です。ただしgetImage()で作ったpreloadは、レスポンシブ画像(layout指定)の<Image>と既定値が違うため、そのままでは別のファイルを指して画像を2回ダウンロードさせます。

この記事では、Astro 7.4.4とsharp 0.35.5で実際にビルドしたHTMLとファイルサイズをもとに、astro:assetsの画像最適化、preload、ページ遷移のprefetch、画像サービスとビルド時間の管理を順に説明します。

まとめ:Astro 7.4で画像・preload・prefetchを設定する要点

  • 画像はsrc/に置いてimportし、<Image>か<Picture>に渡します。public/の画像は最適化されません。
  • <Picture>のフォールバック画像は既定でPNGです。元画像がPNGならfallbackFormat="webp"を指定します(800w相当で558,771バイトが5,742バイトになりました)。
  • srcsetを自動で出すにはlayoutを指定します。既定はnoneで、このままだとwidthsとsizes、またはdensitiesを自分で書かない限り画像は1枚だけです。
  • ファーストビューの画像は、まずpriorityを付けます。fetchpriority="high"などがまとめて付き、HTMLの<img>はブラウザがすぐ見つけるので、多くの場合preloadは要りません。
  • CSS背景やJSで差し込む画像のようにHTMLに出ない画像は、getImage()で生成してpreloadします。layout付きの<Image>と同じファイルを指させるには、fit: "cover"とposition: "center"も渡します。
  • <Picture>はpreloadせずpriorityに任せます。
  • フォントはFonts APIの<Font preload>でpreloadします。既定はfalseです。配列で太さを絞れます。
  • ページ遷移の先読みはprefetch: trueで有効にします。既定のhoverはマウスが乗ってから80ミリ秒後に取得を始めます。

astro:assetsのImage・Pictureによる画像最適化

astro:assetsは、ビルド時(静的サイト)またはリクエスト時(オンデマンド描画)に画像を変換するAstro標準の仕組みです。最適化の対象は、src/からimportした画像と、許可したドメインのリモート画像です。Astro公式の画像ガイドには「Images stored in the public/ folder are never optimized.」と書かれており、public/に置いた画像や素の<img>タグはそのままコピーされるだけです。

Imageの既定出力とqualityの調整

<Image>に幅だけ渡してビルドすると、次のHTMLが出ました。形式は既定でWebPに変わり、loading="lazy"・decoding="async"・width・heightが自動で付きます。幅と高さが入るので、読み込み中にレイアウトがずれません。

---
import { Image } from "astro:assets";
import photo from "../assets/photo.png";
---
<Image src={photo} alt="山の写真" width={800} />
<Image src={photo} alt="山の写真" width={800} quality={40} />
<Image src={photo} alt="山の写真" width={800} format="avif" />

<!-- 1行目の出力 -->
<img src="/_astro/photo.4_AuFKfq_ZCIKgX.webp" alt="山の写真" loading="lazy" decoding="async" width="800" height="450">

元画像(1600×900のPNG、1,704,725バイト)を幅800で出したときのサイズは次のとおりです。合成した滑らかな画像なので圧縮が効きやすい点に注意してください。写真での容量差は、被写体や元画像の圧縮状態、出力品質によって変わります。

指定 出力形式 サイズ(バイト)
既定 WebP 5,742
quality={40} WebP 3,578
format=”avif” AVIF 3,837
fallbackFormat=”jpg” JPEG 13,069
Pictureのフォールバック(既定) PNG 558,771

quality={40}で約38%小さくなりました。ただし品質の劣化は画像ごとに違うので、商品写真など細部が大事な画像は目で確認してから下げてください。

Pictureの既定フォールバック形式と変更指定

<Picture>は<source>に新しい形式を並べ、古いブラウザ向けに<img>のフォールバックを出します。7.4.4のPicture.astroはdefaultFormats = ['webp']、defaultFallbackFormat = 'png'です。元画像がGIF・SVG・JPEGのときだけフォールバックは元の形式を引き継ぎます。PNGのフォールバックはPNGのままです。

<!-- 既定:フォールバックが558,771バイトのPNGになる -->
<Picture src={photo} alt="山の写真" width={800} />

<!-- AVIFとWebPを出し、フォールバックもWebPにする -->
<Picture src={photo} alt="山の写真" width={800} formats={["avif", "webp"]} fallbackFormat="webp" />

WebPは主要ブラウザがすべて対応しているので、フォールバックもWebPで困る場面はほぼありません。フォールバックのPNGは対応ブラウザでは読み込まれません。それでもdistの容量とビルド時間は増えるので、指定しておく方が無駄がありません。

layout指定によるsrcset・sizesの自動生成

画面幅に応じて画像を切り替えるsrcsetとsizesは、layoutをconstrained・full-width・fixedのいずれかにすると自動生成されます。widthsとsizesを明示する方法もあります。layoutの既定はnoneなので、何も指定しないと幅800の画像1枚だけです。layoutは5.10.0で正式機能になりました。

// astro.config.mjs:サイト全体の既定を constrained にする
import { defineConfig } from "astro/config";

export default defineConfig({
  image: {
    layout: "constrained",
    responsiveStyles: true,
  },
});

layout="constrained"で幅800を指定すると、640w〜1600wの7本のsrcsetとsizes="(min-width: 800px) 800px, 100vw"が出ました。responsiveStylesは既定falseで、srcsetの生成とは関係ありません。画像のはみ出しを防ぐCSSを付けるかどうかの設定です。7.0.1からは、このCSSが@layer astro.imagesで包まれ、自分で書いた@layerのスタイルより優先度が下がりました。

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

LCP(ページ内で最も大きい要素が表示されるまでの時間)の対象がヒーロー画像の場合、その画像の取得を早めると数値が改善します。指標の基準値はCore Web Vitalsとは:LCP・INP・CLSの基準値とweb-vitals・CrUX APIでの測り方【2026年10月】で整理しています。Astroで使える手段は、手間の少ない順に次の3つです。

priorityによる読み込み属性の一括指定

5.10.0で<Image>と<Picture>にpriorityが追加されました。付けるとloading="eager"・decoding="sync"・fetchpriority="high"が一度に入ります(astro:assetsのAPIリファレンス)。

<Image src={photo} alt="山の写真" width={800} priority />

<!-- 出力 -->
<img src="/_astro/photo.4_AuFKfq_ZCIKgX.webp" alt="山の写真" loading="eager" decoding="sync" fetchpriority="high" width="800" height="450">

既定の<Image>はloading="lazy"です。LCPの画像に遅延読み込みが付いたままだと、ChromeのLCPリクエスト検出のインサイトは「LCP resources shouldn’t use loading=lazy」と指摘します。ファーストビューの1〜2枚にだけpriorityを付け、それ以外は既定のままにします。

getImageによるpreload用URLの生成

<img>がHTMLに書かれていれば、ブラウザはHTMLを読み進める途中でその画像を見つけます。preloadが効くのは、CSSのbackground-imageやJSで後から差し込む画像のように、HTMLを読んでも見つからない画像です。この場合はgetImage()で最適化済みのURLを作り、<head>にpreloadを書きます。

---
// src/pages/index.astro
import { getImage } from "astro:assets";
import Layout from "../layouts/Layout.astro";
import hero from "../assets/hero.png";

const bg = await getImage({ src: hero, format: "webp", width: 1600 });
---
<Layout>
  <link slot="head" rel="preload" as="image" href={bg.src} fetchpriority="high" />
  <section class="hero" style={`background-image: url(${bg.src})`}>...</section>
</Layout>
---
// src/layouts/Layout.astro:名前付きスロットで head に差し込む
---
<html lang="ja">
  <head>
    <meta charset="utf-8" />
    <slot name="head" />
  </head>
  <body><slot /></body>
</html>

preloadの<link>にもfetchpriority="high"を付けます。ChromeのLCPリクエスト検出のインサイトには「fetchpriority=high should be applied to the image preload request」という判定項目があり、preloadしただけでは優先度が上がりきらないためです(Chrome DevToolsのLCPリクエスト検出の解説)。preloadしたURLと実際に表示するURLが1文字でも違えば、同じ画像を2回ダウンロードすることになります。ビルド後にdistのHTMLを開き、hrefとsrcが同じファイル名か確かめてください。

layout付き画像をpreloadするとURLがずれる理由

レスポンシブ画像をpreloadするときは、<link>のimagesrcsetとimagesizesに<img>と同じ値を入れます(web.devのレスポンシブ画像のpreload解説)。ここでgetImage()にlayout: "constrained"だけ渡すと、<Image layout="constrained">とは別のファイル群ができました。7.4.4で同じ元画像・幅800をビルドした結果です。

呼び出し 800wのファイル名(末尾) Imageと一致
<Image layout=”constrained”> _ZjyR55.webp -
getImage(layoutのみ) _ZAYj8n.webp しない
getImage(layout+fit+position) _ZjyR55.webp する

原因はImage.astroの既定値にあります。layoutがnone以外のとき、コンポーネントはfitに"cover"、positionに"center"を自動で入れます(設定のimage.objectFit・image.objectPositionがあればその値)。getImage()はこの補完をしないので、変換パラメータが変わり、ファイル名のハッシュも変わります。preload側に同じ値を明示すれば一致します。

---
import { Image, getImage } from "astro:assets";
import hero from "../assets/hero.png";
import Layout from "../layouts/Layout.astro"; // 前節の Layout(head スロット付き)

// Image コンポーネントが自動で補う fit と position を明示する
const opts = { src: hero, width: 800, layout: "constrained", fit: "cover", position: "center" } as const;
const pre = await getImage(opts);
---
<Layout>
<link slot="head" rel="preload" as="image" href={pre.src}
  imagesrcset={pre.srcSet.attribute} imagesizes={pre.attributes.sizes} fetchpriority="high" />

<Image {...opts} alt="山の写真" priority />
</Layout>

確実なのは、preloadと表示を同じgetImage()の結果から作ることです。<img src={pre.src} srcset={pre.srcSet.attribute} {...pre.attributes} alt="山の写真" loading="eager" fetchpriority="high">と書けば、URLがずれる余地がありません。image.objectFitを設定で変えている場合は、その値をfitに渡します。

Pictureでpreloadよりpriorityを優先する理由

<Picture>の画像はpreloadしない方が安全です。<picture>はブラウザが対応形式(type)を見て<source>を選びますが、preloadではこの選択を表現できません。web.devも、形式を切り替える<picture>ではpreloadを避け、priority hints(fetchpriority)を使うよう勧めています。形式を決め打ちしてpreloadすると、ブラウザが別の形式を選んだときに使われないファイルまで取得します。<Picture priority>なら、フォールバックの<img>にfetchpriority="high"が付き、ブラウザが選んだ形式だけが優先されます。

Fonts APIによるWebフォントのpreload設定

「astro preload」で探している人のもう一つの用途がWebフォントです。Astro 6.0でFonts API(設定のfonts)が実験機能から正式機能になり、@font-faceの生成、フォールバック、preloadをAstroが出力するようになりました。preloadは<Font>コンポーネントのpreloadで指定し、既定はfalseです。

// astro.config.mjs
import { defineConfig, fontProviders } from "astro/config";

export default defineConfig({
  fonts: [{
    provider: fontProviders.local(),
    name: "Inter",
    cssVariable: "--font-inter",
    options: {
      variants: [
        { src: ["./src/fonts/inter-latin-400-normal.woff2"], weight: 400, style: "normal" },
        { src: ["./src/fonts/inter-latin-700-normal.woff2"], weight: 700, style: "normal" },
      ],
    },
  }],
});
---
// src/layouts/Layout.astro の head
import { Font } from "astro:assets";
---
<!-- 400だけpreloadする(preload だけ書くと 400 と 700 の2本) -->
<Font cssVariable="--font-inter" preload={[{ weight: 400 }]} />

<!-- 出力 -->
<link rel="preload" href="/_astro/fonts/2605f035fc379c64.woff2" as="font" type="font/woff2" crossorigin>

preloadだけを書くと、登録した2本のwoff2がどちらもpreloadされました。Astro公式のフォントガイドも、preloadは他の重要なリソースの取得を妨げるので、ファーストビューの表示に要るフォントだけに絞るよう書いています。表示に使う要素には、別途CSSでfont-family: var(--font-inter);を指定します。preloadする太さはファーストビューの内容で決めます。大きな見出しがLCP要素になるページでは、その見出し用の太字も候補にします。日本語フォントは1ファイルが数MBになることがあり、丸ごとpreloadするとLCPがかえって遅れます。

prefetchによる遷移先ページの先読み設定

preloadが「このページで使うリソース」を先に取るのに対し、prefetchは「次に開きそうなページ」のHTMLを先に取得しておく仕組みです。Astroでは設定でprefetch: trueにすると、data-astro-prefetchを付けたリンクが対象になります。各ページに入るスクリプトは、今回の検証構成で2,478バイトでした。

// astro.config.mjs
export default defineConfig({
  prefetch: {
    prefetchAll: false,
    defaultStrategy: "hover",
  },
});

<!-- リンクごとに戦略を指定する -->
<a href="/pricing/" data-astro-prefetch>料金</a>
<a href="/docs/" data-astro-prefetch="viewport">ドキュメント</a>
<a href="/contact/" data-astro-prefetch="false">問い合わせ</a>

4つの戦略が取得を始めるタイミング

7.4.4のprefetchスクリプトを読むと、戦略ごとの動きは次のとおりです。

戦略 取得を始めるきっかけ 向く場面
hover(既定) マウスが乗るかフォーカスして80ms後 通常のナビゲーション
tap touchstart・mousedownの直後 リンクが多い一覧
viewport 画面に入って300ms後 スマホで次に読む記事
load ページ読み込み時にload対象リンク リンクが数本の小さなサイト

回線の節約モード(saveData)や2G回線と判定されたときは、先読み対象のリンクはtap戦略にフォールバックし、タップ時に取得します。loadをページ数の多いサイトで使うと、読まれないページまで取得してサーバーの転送量が増えます。

ClientRouter使用時の既定とSafariの扱い

ページ遷移アニメーション用の<ClientRouter />を置いたページでは、prefetchが自動で有効になり、prefetchAll: true(全リンクが対象)が既定になります。対象を絞りたいときは設定でprefetchAll: falseを明示します。

Safariは<link rel="prefetch">に対応していないため、Astroはfetch()で取得します。Astro公式のprefetchガイドによれば、この場合はCache-Control・Expires・ETagのいずれかのキャッシュヘッダーが無いと、取得したHTMLが遷移時に使われません。ホスティング側でHTMLにno-storeを付けているサイトでは、Safariでは効かないと考えてください。なお、prefetch()関数のeagernessオプションはexperimental.clientPrerenderを有効にしたときだけ使われ、7.4.4でも実験機能のままです。

画像サービスの選択とビルド時間の管理

「astro image service」は、画像を実際に変換する部品のことです。既定はsharpで、image.serviceで差し替えます。画像が多いサイトでは、画質よりもビルド時間が先に問題になります。

sharpとpassthroughImageServiceの使い分け

sharpが動かない実行環境(公式ガイドの例ではCloudflare)でオンデマンド描画をする場合は、変換をしないpassthroughImageService()に切り替えます。変換はされませんが、<Image>の幅・高さの自動付与とaltの必須チェックは残ります。

// astro.config.mjs
import { defineConfig, passthroughImageService } from "astro/config";

export default defineConfig({
  image: {
    service: passthroughImageService(),
  },
});

画像キャッシュの再利用と–timingsによる時間計測

Astroは変換済みの画像をcacheDir(既定は./node_modules/.astro)に保存し、次のビルドで使い回します。今回の検証では、20件の画像変換がキャッシュ無しで2.56秒、キャッシュ有りで22ミリ秒でした。CIで毎回node_modulesを作り直していると、この効果が消えます。GitHub Actionsならnode_modules/.astroをキャッシュ対象に入れておきます。

7.4.0で追加されたastro build --timingsを付けると、ビルドの最後に画像変換・ページ描画・コンパイルの時間が一覧で出ます。画像が遅いと分かれば、formatsを減らす、layoutのwidthsを絞る、フォールバックPNGをやめる、の順に効きます。たとえば<Picture layout="constrained" formats={["avif", "webp"]}>をPNGフォールバックのまま使うと、1枚の元画像からAVIF・WebP・PNGが7本ずつ、計21ファイル生成されました。

PageSpeed Insightsの点数と実ユーザー指標の優先順位

PageSpeed Insightsのラボスコアは、Lighthouse 13.6.0の設定でTBT 30・LCP 25・CLS 25・FCP 10・Speed Index 10の重み付けです(Lighthouseの既定設定ファイル)。INPは重み0で、点数に入りません。Astroはクライアント側JSを抑えやすい構成ですが、スコアは画像・フォントに加え、サーバー応答、CSS、追加したJSなどにも左右されます。priority・preload・画像形式の調整は主にLCPの改善候補です。画像によるCLSには、width・heightやCSSのaspect-ratioによる表示領域の確保で対処します。各カテゴリの配点の読み方はLighthouseのSEOスコア・Best Practicesの配点と見方|5カテゴリの採点基準【v13.5.0】で解説しています。

100点そのものを目標にする必要はありません。Googleはページエクスペリエンスの解説で「There is no single signal.」と書き、ページエクスペリエンスが劣っていても関連性の高いコンテンツを表示するとしています。Search ConsoleのCore Web Vitalsレポートが使うのも実ユーザーのデータ(CrUX)で、ラボの点数ではありません。90点台からさらに上げる作業より、実際のアクセスでLCPとINPが「良好」に入っているかを確認する方が先です。INPの改善手順はINPとは?Core Web Vitalsの応答性指標の基準値と3区間・改善手順にまとめています。

次の場面では、Astro側の調整より先に別の原因を疑います。サーバーの応答(TTFB)が遅い、広告やタグマネージャーのJSが重い、ヒーロー画像を外部CMSから毎回取得している、のいずれかです。サイト全体を効き目の順に直す手順はサイトの表示速度を改善する手順:画像・キャッシュ・JS・サーバーを効き目順に直す【2026年10月】を参照してください。既存サイトをAstroで作り直して表示速度を上げたい場合は、Webサイトリニューアルとしてご相談いただけます。

よくある質問

npmのastro-preloadパッケージとAstroのpreloadは同じものですか?

別物です。astro-preloadは、リモートの画像やIconifyのアイコンをビルド時にダウンロードしてpublic/に保存する第三者製のインテグレーションです。npmで公開されている最新版は1.1.2(2023年7月1日公開)で、それ以降の版はnpmに出ていません。<link rel="preload">を出すものではないので、LCP対策にはpriorityかgetImage()を使います。

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

されません。public/のファイルはそのままビルド結果にコピーされます。最適化したい画像はsrc/assets/などに移してimportし、<Image>に渡してください。faviconやOGP画像のように、URLを固定したいファイルだけをpublic/に残します。

preloadとprefetchはどう使い分けますか?

preloadは表示中のページで必ず使うリソース(ヒーロー画像・本文フォント)を高い優先度で先に取得します。prefetchは次に開くかもしれないページを低い優先度で取得します。preloadを付けすぎると本当に必要なリソースの取得が遅れ、prefetchを付けすぎると読まれないページの転送が増えます。

@astrojs/imageはまだ使えますか?

使えません。@astrojs/imageは0.18.0でnpm上の非推奨になり、「deprecated in favor of astro:assets」と表示されます。Astro 3.0以降はastro:assetsが標準で、npx astro add imageを前提にした古い手順は今の版では動きません。

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

7.0.1でレスポンシブ画像のCSSが@layer astro.imagesで包まれました。自分のCSSを@layerで書いている場合、以前は負けていたスタイルが勝つようになり、画像のサイズや配置が変わることがあります。上げたあとはファーストビューの画像の表示を確認してください。priorityとlayoutは5.10.0から使えるので、5系の後半から上げる場合は書き換えは要りません。

関連記事

お気に入りに入れた記事の一覧

一創の公式 SNS

X では毎朝の新着記事と IT ニュースの速報を、YouTube・TikTok・Instagram・LinkedIn・Facebook では記事の解説動画を配信しています。

この記事は以下の記事からリンクされています

資料請求

RELATED POSTS 関連記事

目次