現在も開発が進められていますが Astro2.1 から新たに画像の最適化を行うビルドインツール Assets が登場しました。これまで Astro では@astrojs/image を追加インストールすることで画像の最適化を行うことはできました。Assets は@astrojs/image の後継として開発が行われており Astro コンポーネントだけではなく Markdown ファイルでも簡単に画像最適化を行うことができます。

開発中のため今後 API の変更の可能性もありますが Assets によってどのような画像の最適化が行われるのかというコアな部分は大きく変わらないと思うので Assets を利用できる環境を構築して動作確認を行っています。

Astro の現在の最新バージョンは 2.6 ですが今後も画像に関する更新が行われれば本文書も更新を行っていく予定です。現在のバージョンでは Image コンポーネントのみ利用することができますが@astrojs/image で利用できる Picture コンポーネントもドキュメントに記載されている”Currently, the built-in assets feature does not include a \<Picture /\> component.”から近い将来 Picture コンポーネントも利用できるようになると思われます。


Astro3.0 がリリースされ、Assets 機能は Stable となりました。

環境の構築

プロジェクトの作成

Astro の Assets を確認するために npm create astro コマンドでプロジェクトの作成を行います。コマンド実行後ににいくつか質問がありますが質問の一つであるテンプレートは”samle files”を選択しています。残りの質問はすべてデフォルトを選択しています。プロジェクト名には任意の名前をつけることができますがここでは astro-image という名前を設定しています。


 % npm create astro@latest

╭─────╮  Houston:
│ ◠ ◡ ◠  Let's make the web a better place!
╰─────╯

 astro   v2.5.1 Launch sequence initiated.

   dir   Where should we create your new project?
         ./astro-image

  tmpl   How would you like to start your new project?
         Include sample files
      ✔  Template copied

  deps   Install dependencies?
         Yes
      ✔  Dependencies installed

    ts   Do you plan to write TypeScript?
         Yes

   use   How strict should TypeScript be?
         Strict
      ✔  TypeScript customized

   git   Initialize a new git repository?
         Yes
      ✔  Git initialized

  next   Liftoff confirmed. Explore your project!

         Enter your project directory using cd ./astro-image 
         Run npm run dev to start the dev server. CTRL+C to stop.
         Add frameworks like react or tailwind using astro add.

         Stuck? Join us at https://astro.build/chat

╭─────╮  Houston:
│ ◠ ◡ ◠  Good luck out there, astronaut! 🚀

コマンドが完了するとastro-imageフォルダが作成されるので移動を行いpackage.jsonの中身を確認しておきます。


{
  "name": "astro-image",
  "type": "module",
  "version": "0.0.1",
  "scripts": {
    "dev": "astro dev",
    "start": "astro dev",
    "build": "astro build",
    "preview": "astro preview",
    "astro": "astro"
  },
  "dependencies": {
    "astro": "^2.4.1"
  }
}

コンポーネントの作成

動作確認を行うためのコンポーネントの作成を行います。componentsフォルダにAssets.astroコンポーネントを作成します。


<h1>画像</h1>

作成したAssetsコンポーネントをimportするためにpagesフォルダのindex.astroを以下のように書き換えます。


---
import Layout from '../layouts/Layout.astro';
import Assets from '../components/Assets.astro';
---

<Layout title="Welcome to Astro.">
  <main>
    <Assets />
  </main>
</Layout>

開発サーバを起動するためにnpm run devコマンドを実行します。Assets.astroに記述した内容が表示されます。

初期画面
初期画面

画像の表示

Assetsや@astrojs/imageなどの機能を利用しなくても画像を表示することはできます。画像最適化の機能を利用しない場合での画像の表示方法を確認しておきます。

publcフォルダ

デフォルトから存在するpublicフォルダに画像ファイルbird.jpgを保存します。

保存したファイルのパスをimgタグのsrc属性に設定します。src属性に設定するパスに注意する必要があり、/publicフォルダに保存したファイルをsrc属性に設定する場合はpublicを抜かして/bird.jpgと設定します。


<h1>画像</h1>
<div>
  <img src="/bird.jpg" alt="a bird" />
</div>

ブラウザ上にはbird.jpgの鳥の画像が表示されます。

publicフォルダの画像を表示
publicフォルダの画像を表示

デベロッパーツールで要素を確認すると下記のように設定が行われています。


<img src="/bird.jpg">

リモートサイト

リモートサイトに保存されている画像を利用する場合の動作確認を行います。src属性にはそのままURLを指定します。


<h1>画像</h1>
<div>
   <img src="https://picsum.photos/1280/852" />
</div>
リモートサービスからのダミー画像
リモートサービスからのダミー画像

デベロッパーツールで要素を確認すると下記のように設定が行われています。


<img src="https://picsum.photos/1280/852">

importによる画像の設定

srcフォルダにimagesフォルダを作成してその下にbird.jpgファイルを保存します。

importする際、Assets.astroファイルからの相対パスを利用して設定しています。


---
import bird from '../images/bird.jpg';
---

<h1>画像</h1>
<div>
  <img src={bird} />
</div>
publicフォルダの画像を表示
imagesフォルダの画像を表示

デベロッパーツールで要素を確認すると下記のように設定が行われています。


<img src="/src/images/bird.jpg">

ビルド後は?

3 つの方法を利用して画像の設定を行いましたがビルドを行った際のファイルの扱いにはどのような違いがあるのか確認します。ビルドは npm run build コマンドで行います。ビルドを実行するとプロジェクトフォルダ直下に dist フォルダが作成されそのフォルダにビルドが完了したファイルが保存されます。ビルド後に npm run preview コマンドを実行するとビルド後のファイルを利用して表示されます。

publicの場合

importを行った場合のdistフォルダの中を確認するとbird.jpgファイルがdistフォルダ直下に作成されていることがわかります。

ブラウザから確認するとビルド前と変わらずsrc属性に/bird.jpgが設定されています。


<img src="/bird.jpg">

リモートサイトの場合

importを行った場合のdistフォルダの中を確認すると画像に関するファイルは存在しません。

ブラウザから確認するビルド前と変わらずsrc属性にはリモートサーバのURLが設定されています。


<img src="https://picsum.photos/1280/852">

importの場合

importを行った場合のdistフォルダの中を確認すると_astroフォルダの中にbird.6fede37f.jpgというファイル名で保存されていることがわかります。

デベロッパーツールで要素を確認すると_astroフォルダにあるファイルがsrc属性に設定されています。


<img src="/_astro/bird.6fede37f.jpg">

Assetsの動作確認

assetsフォルダを利用しない状態での画像の設定方法が確認できたので次はAssets機能による画像の設定方法を確認します。

設定

srcフォルダ直下にassetsフォルダを作成します。assetsフォルダ下にbird.jpgファイルを保存します。Assets機能を利用するためにastro.config.mjsファイルでAssetsを有効化する必要があります。開発中の機能なのでexperimentalプロパティにassets:trueを設定します。


import { defineConfig } from 'astro/config';

// https://astro.build/config
export default defineConfig({
  experimental: {
    assets: true,
  },
});

.env.d.tsファイルを下記の設定に変更します。


/// <reference types="astro/client-image" />

画像の表示

Assetsを利用して画像を表示する場合はImageコンポーネントを利用して以下のように行うことができます。


---
import { Image } from 'astro:assets';
import bird from '../assets/bird.jpg';
---

<h1>画像</h1>
<div>
  <Image src={bird} alt="a bird" />
</div>

設定後ブラウザで確認すると鳥の画像が表示されて一見何も変化ありません。

デベロッパーツールで要素を確認するとこれまでとは異なりimgタグにwidth, height, loading, decoding属性が追加されています。わかりにくいですがsrcに設定されてある文字列の最後に”webp”を確認することができます。


<img src="/_image?href=%2Fsrc%2Fassets%2Fbird.jpg%3ForigWidth%3D1280%26origHeight%3D852%26origFormat%3Djpg&f=webp" alt="a bird" width="1280" height="852" loading="lazy" decoding="async">

width, height を設定することで画像のダウンロードに時間がかかる場合にブラウザ上に画像の表示領域を確保してくれるため Layout Shift を防ぐことができます。

画像の下にコンテンツがあり width, height が設定されていない場合に画像のダウンロードに時間がかかると画像が表示される場所にコンテンツが表示され、画像が表示されるとコンテンツがその下に移動する Layout Shift が発生します。

loading を lazy に設定することで画像の遅延読み込みを行います。アクセス時にブラウザのビューポート外に配置されている画像はユーザがスクロールしてビューポートに近づいたタイミングで画像の読み込みが行われます。decoding の async は画像を表示するためのデコード処理を画像以外のコンテンツ処理を妨げずに非同期で行うための設定です。2 つの値を設定することでページの表示速度を最適化しています。

src 属性の文字列の最後の webp の確認ですがブラウザ上の画像を右クリックで別名保存かドラッグ&ドロップで保存してください。拡張子が jpg ではなく webp として保存されることが確認できます。

さらにデベロッパーツールのネットワークタブで Type を確認してください。Type が webp になっていることがわかります。webp は圧縮率が高いためファイルサイズを小さくすることができます。

ネットワークタグの確認
ネットワークタグの確認

ネットワークタブの Size 列を確認してください。62.8kB と表示されています。これは画像のダウンロードサイズです。OS 上での画像のサイズは 131KB なので webp に変換され圧縮されていることがわかります。

Assets 機能ではファイルタイプの変換によってファイルサイズを小さくしていることもわかります。

デフォルトでの jpg から webp へのファイルの変換にはSqooshが利用されています。デプロイ先が Node の場合は別の変換ツールであるsharpを利用することもできます。

ここまでの動作確認で Assets 機能を利用することでさまざまな画像最適化が行われていることが理解できました。

ビルドの実行

Assets 機能を利用した場合にビルドを実行するとどのようなファイルが作成されるのか確認します。

ビルドを実行すると dist フォルダには bird.6fede37f.jpg だけではなく bird.6fede37f_1xV4Cy.webp ファイルも作成されていることが確認できます

importから戻される値

Assets機能を利用した場合にimport文で戻されるbirdの値を確認します。


import bird from '../assets/bird.jpg';
console.log(bird);

npm run devコマンドを実行したターミナルにはsrc, width, height, formatプロパティで構成されたオブジェクトが表示されます。


{
  src: '/src/assets/bird.jpg?origWidth=1280&origHeight=852&origFormat=jpg',
  width: 1280,
  height: 852,
  format: 'jpg'
}

そのためimportした画像を利用した場合はImageタグではなくimgタグで下記のように設定するとエラーとなります。


<img src={bird} />

imgタグを利用する場合は下記のように戻り値のbird.srcをsrcに設定することでブラウザ上に画像が表示されます。


<img src={bird.src} width={bird.width} height={bird.height} />
astro.config.mjsでassetsを設定していない場合にimportしたファイルの戻り値はそのままファイルのパス(/src/assetss/bird.jpg)になります。

戻り値とは関係ありませんがassetsフォルダではなくimagesフォルダに作成したbird.jpgを利用してもAssets機能は使えます。


---
import { Image } from 'astro:assets';
import bird from '../images/bird.jpg';
// import bird from '../assets/bird.jpg';
---

<h1>画像</h1>
<div>
  <Image src={bird} alt="a bird" />
</div>

publicフォルダの画像設定

Image コンポーネントの src にそのまま public フォルダに保存しているファイルのパスを設定します。public フォルダのファイルを指定する場合は width, height の設定が必須となります。省略するとエラーとなります。


---
import { Image } from 'astro:assets';
---

<h1>画像</h1>
<div>
  <Image src="/bird.jpg" width={1280} height={852} alt="a bird" />
</div>

デベロッパーツールの要素で確認するとsrcはそのままファイルのパスが設定され、loadingとdecodingだけ設定されていることがわかります。


<img src="/bird.jpg" alt="a bird" width="1280" height="852" loading="lazy" decoding="async">

リモートサイトの画像設定

リモートサイトの画像を設定した場合の動作確認も行います。ファイルを指定する場合はwidth, heightの設定が必須となります。省略するとエラーとなります。


---
import { Image } from 'astro:assets';
---

<h1>画像</h1>
<div>
  <Image
    src="https://picsum.photos/1280/852"
    width={1280}
    height={852}
    alt="a bird"
  />
</div>

デベロッパーツールの要素で確認するとsrcはそのままリモートファイルのパスが設定され、loadingとdecodingだけ設定されていることがわかります。


<img src="https://picsum.photos/1280/852" alt="a bird" width="1280" height="852" loading="lazy" decoding="async">

publicフォルダとリモートサイトの設定からimportした場合のみ画像の変換による最適化が行われることがわかります。

qualityやformatを明示的に設定しても変化はありません。

Imageコンポーネントのプロパティ

width, height

width, height はローカルに保存されているファイルを import した場合には自動で設定が行われますが/publc フォルダにある画像やリモートサイトの画像を利用する場合は指定する必要がります。

/public フォルダに保存してある bird.jpg を import ではなくパスを Image コンポーネントの src に設定します。


---
import { Image } from 'astro:assets';
---

<h1>画像</h1>
<div>
  <Image src="/bird.jpg" alt="a bird" />
</div>

ブラウザで確認すると”Missing image dimensions”で”Missing width and height attributes for /bird.jpg. When using remote images, both dimensions are always required in order to avoid CLS.”

MissingImageDimension Exception
MissingImageDimension Exception

エラーからリモートの画像を利用する場合には width と height の設定が必要であることがわかります。


<h1>画像</h1>
<div>
  <Image src="/bird.jpg" alt="a bird" width={1280} height={852} />
</div>

設定後はエラーは解消され画像が表示されます。

/publicだけではなくリモートサイトの場合も下記のようにwidth, heightを設定することで表示されます。


---
import { Image } from 'astro:assets';
---

<h1>画像</h1>
<div>
  <!-- <Image src={bird} alt="a bird" /> -->
  <Image
    src="https://picsum.photos/1280/852"
    alt="a bird"
    width={1280}
    height={852}
  />
</div>

format

デフォルトではjpgファイルからwebpファイルが作成されていることが確認できました。formatプロパティにファイルタイプを設定することで作成されるファイルを指定することができます。webpと同様に高い圧縮率を持つavifを利用します。


<h1>画像</h1>
<div>
  <Image src={bird} format="avif" alt="a bird" />
</div>

デベロッパーツールのネットワークタグを確認するとTypeがavifに変更になっておりファイルサイズも変わっていることが確認できます。

avifをformatに指定した場合
avifをformatに指定した場合

formatをpngに指定した場合も確認を行ってみましたがサイズがかなり大きくなっていることがわかります。

formatにpngを指定した場合
formatにpngを指定した場合

quality

quality の値によって画像の画質を調整することができます。preset(low, mid, high,max)と数値(0 から 100)で設定することができます。

preset で low を設定します。


<Image src={bird} quality="low" alt="a bird" />

Sizeを確認すると小さくなっていることがわかります。

qualityの値をlowにした場合
qualityの値をlowにした場合

qualityの値を5に設定します。


<Image src={bird} quality={5} alt="a bird" />

さらにサイズが小さくなっていることがわかります。

qualityの値を5に
qualityの値を5に

ブラウザ上の画像は少し粗さが目立つようになっていることが確認できます。このように quality を利用することでファイルサイズを小さくすることはできますが画像の質が悪くなることもわかります。

class

Imageコンポーネントにはclassを設定することができます。下記のようにclassを利用することで親要素の大きさに合わせて画像の幅を設定することもできます。


---
import { Image } from 'astro:assets';
import bird from '../assets/bird.jpg';
---

<h1>画像</h1>
<div style="width:500px">
  <Image src={bird} class="my-class" alt="a bird" />
</div>
<style>
  .my-class {
    width: 100%;
    height: auto;
  }
</style>

ブラウザで確認するとclassが適用されていることがわかります。

classを利用して親要素の幅に合わせる
classを利用して親要素の幅に合わせる

Markdownファイルでの利用

Astro は pages フォルダに md ファイルを保存してもルーティングが設定され markdown ファイルとして認識してくれるので pages フォルダに image.md ファイルを保存して assets フォルダにある bird.jpg ファイルを設定します。


---
---

# Astro Assets

![a bird](../assets/bird.jpg)

ブラウザから/imageにアクセスすると画像が表示されることが確認できます。

markdownファイルでの画像
markdownファイルでの画像

デベロッパーツールで要素を確認するとAssets機能が有効になっていることがわかります。


<img alt="a bird" src="/_image?href=%2Fsrc%2Fassets%2Fbird.jpg%3ForigWidth%3D1280%26origHeight%3D852%26origFormat%3Djpg&f=webp" width="1280" height="852" loading="lazy" decoding="async">
assetsフォルダではなくimagesフォルダに保存したファイルでも同様にAssets機能が有効になります。

/publicファイルにあるbird.jpgを設定した場合はAssets機能は有効になりません。リモートサイトのファイルを指定した場合も同様です。


---
---

# Astro Assets

![a bird](/bird.jpg)

Content Collections での利用

Content Collections を利用することで Markdown ファイルの frontmatter に設定した画像にも Assets の機能はによる最適化を行うことができます。動作確認を行うために Content Collections の設定を行います。

Content Collections の設定

src フォルダの下に content フォルダを作成して blog フォルダを作成します。blog フォルダの下に Markdown ファイルの astro.md ファイルを作成して以下の内容を記述します。cover に画像の相対パスを指定します。frontmatter にした cover の画像を Assets 機能で最適化します。


---
title: Astro Assets
author: John
cover: ../../assets/bird.jpg
description: Astro2.1で追加されたAssetsについて
publishDate: 2023/05/23
---

# Astro のインストール

Astro のインストールを行います。

src/contentフォルダの下にconfig.tsファイルを作成して以下のコードを記述します。Zodライブラリを利用してfrontmatterのバリデーションを行いこの設定から型定義を作成します。coverの型としてimageを設定する必要があります。


import { z, defineCollection } from 'astro:content';
const blog = defineCollection({
  schema: ({ image }) =>
    z.object({
      title: z.string(),
      author: z.string(),
      description: z.string(),
      cover: image(),
      publishDate: z.string().transform((str: string) => new Date(str)),
    }),
});
export const collections = {
  blog,
};

型の情報は.astroフォルダのtypes.d.tsファイルに保存されますがデフォルトでは.astroフォルダは存在しないためconfig.tsファイルを作成後にnpm run devコマンドを実行すると作成されます。作成後も型に関するメッセージが表示される場合はnpx astro syncを実行してください。手動で.astroフォルダ内のファイルが更新されます。

画像の最適化

pages/index.astroファイルにブログ一覧を表示するための設定を行います。content/blog以下に保存したMarkdownの中身はgetCollection関数によって取得することができます。取得したデータをmap関数を利用して展開します。frontmatterの情報はblog.dataに含まれているのでImageコンポーネントのsrcにblog.data.coverを設定します。


---
import { Image } from 'astro:assets';
import Layout from '../layouts/Layout.astro';
import { getCollection } from 'astro:content';

const blogs = await getCollection('blog');
---

<Layout title="top">
  <h1>ブログ記事一覧</h1>
  <ul style="width:100px">
    {
      blogs.map((blog) => (
        <li>
          <Image src={blog.data.cover} alt="test" class="my-cover" />
          <a href={`/blog/${blog.slug}`}>{blog.data.title}</a>
        </li>
      ))
    }
  </ul>
</Layout>
<style>
  .my-cover {
    width: 100%;
    height: auto;
  }
</style>

一覧に画像を表示することができました。

frontmatterで指定した画像の表示
frontmatterで指定した画像の表示

デベロッパーツールで要素を確認すると以下のようにAssetsによる最適化が行われていることがわかります。


<img src="/_image?href=%2Fsrc%2Fassets%2Fbird.jpg%3ForigWidth%3D1280%26origHeight%3D852%26origFormat%3Djpg&f=webp" alt="test" class="my-cover astro-J7PV25F6" width="1280" height="852" loading="lazy" decoding="async">

blog.data.coverには以下の値が含まれています。


{"src":"/src/assets/bird.jpg?origWidth=1280&origHeight=852&origFormat=jpg",
"width":1280,"height":852,"format":"jpg"}

Content Collectionsを利用することでMarkdownファイルのfrontmatterに設定した画像をAssetsで最適化して表示できることがわかりました。

getImage関数

Assets機能では関数を利用して画像の最適化処理を行うことができます。imporした画像の情報をgetImage関数の引数のsrcプロパティに設定を行います。


---
import { getImage } from 'astro:assets';
import bird from '../assets/bird.jpg';
const optimizedBackground = await getImage({ src: bird });
console.log(optimizedBackground);
---

npm run devコマンドを実行したターミナルにはgetImageの戻り値が表示されます。


{
  rawOptions: {
    src: {
      src: '/src/assets/bird.jpg?origWidth=1280&origHeight=852&origFormat=jpg',
      width: 1280,
      height: 852,
      format: 'jpg'
    },
    format: 'webp'
  },
  options: {
    src: {
      src: '/src/assets/bird.jpg?origWidth=1280&origHeight=852&origFormat=jpg',
      width: 1280,
      height: 852,
      format: 'jpg'
    },
    format: 'webp'
  },
  src: '/_image?href=%2Fsrc%2Fassets%2Fbird.jpg%3ForigWidth%3D1280%26origHeight%3D852%26origFormat%3Djpg&f=webp',
  attributes: { width: 1280, height: 852, loading: 'lazy', decoding: 'async' }
}

src, attributesなどimgタグで利用可能な情報が含まれていることがわかります。srcを下記のように利用することでブラウザ上に画像が表示されます。


---
import { getImage } from 'astro:assets';
import bird from '../assets/bird.jpg';
const optimizedBackground = await getImage({ src: bird });
console.log(optimizedBackground.src);
---

<h1>画像</h1>
<div>
  <img src={optimizedBackground.src} />
</div>

attributesの値を利用することでlazyやdecodingの値もimgタグに設定できます。


---
import { getImage } from 'astro:assets';
import bird from '../assets/bird.jpg';
const optimizedBackground = await getImage({ src: bird });
---

<h1>画像</h1>
<div>
  <img
    src={optimizedBackground.src}
    width={optimizedBackground.attributes.width}
    height={optimizedBackground.attributes.height}
    loading={optimizedBackground.attributes.loading}
    decoding={optimizedBackground.attributes.decoding}
  />
</div>

getImageの引数ではImageコンポーネントで利用していたqualityなどのプロパティも利用できます。


const optimizedBackground = await getImage({ src: bird, quality: 5 });

sharpの利用

デフォルトでは画像の変換にsqooshを利用していますがデプロイ先がNodeの場合はsharpを利用することができます。利用方法はsharpのインストールを行い、astro.config.mjsファイルに追加を行います。


 % npm install sharp

import { defineConfig } from 'astro/config';

// https://astro.build/config
export default defineConfig({
  experimental: {
    assets: true,
  },
  image: {
    service: sharpImageService(),
  },
});

現在の Assets 機能では Image コンポーネントしかないため sharp に変更しても機能の違いがわかりませんが@astrojs/image では Picture コンポーネントの機能で sharp でしか利用できない機能があります。将来的に Assets 機能で Picture コンポーネントが利用できるようになった時に説明を追加します。