Tech系サービスやガジェットの使い心地、自分の作業環境、資産運用について気が向いたときに記録を残しています。

記事内のAmazonアソシエイト適格販売及び、Google Adsenseでお小遣いを得ています。

Astro MDX integrationで画像をレスポンシブ対応にする方法

自転車ブログをGatsbyからAstroに書き換えた。

blog.gensobunya.net

新興フレームワークだけあり、Gatsbyのように豊富なプラグインによる便利な設定というものはないので、殆どの機能を自分で実装する必要がある。

Head APIがなく、レイアウトコンポーネントから必要な要素を全てPropsで渡してheadタグに記述したり、クライアント処理をAstroでやるのはつらいのでReactを使ったりと、Gatsbyに慣れているといろいろ面倒なところが多く感じた。

その中でも、ナレッジが少なく(というかundocumented)でデフォルト処理してくれない、MDX内に相対パスで記述した画像をレスポンシブ化する部分だけ抜粋してメモ。

背景

AstroにはContent Collectionという機能があり、srcディレクトリ内に配置された画像を自動的にフォーマット変換・レスポンシブ対応で自動的に複数サイズの画像をsharpレンダリングする機能がある。

.astroコンポーネントの場合は、組み込みの<Image>, <Picture>タグを通じてこの最適化をコントロールできる。

docs.astro.build

一方で、このContent Collectionは.md, .mdxファイル内の画像リンクも最適化してくれるのだが、この最適化は最適化と名ばかりのオリジナルサイズのwebpを吐き出すだけである(しかもフォールバックもない)。

Content Collectionには(ドキュメントを読む限り)グローバル設定が存在しないうえ、gatsby-plugin-image のようなキャプション設定機能も存在していないため、自前でこれらを実装する必要がある。

Astroの書き味はNext.jsに似ていて、シンプルなコア機能やコンポーネントを提供する方針のようだ。

前提

  • Astro.3.x
  • Content Collection
  • @astro/mdx integration >1.1.0

実装

実は、@astro/mdx インテグレーションにはCustom Componentのマッピングimgを指定した場合、ContentCollectionによって処理された画像に限り AstroのImageMetadataタイプがPropsとして渡される仕様になっている。

github.com

これを知っていると非常にシンプルに記述できる。この情報にたどり着くまでは自分でrehype, remarkプラグインを書かねばならないのかと絶望していたところ。

---
//MdxPicture.astro
import type { ImageMetadata } from "astro"
import { Picture } from "astro:assets"
type Props = {
  src: string | ImageMetadata
  alt: string
}
const { src, alt } = Astro.props
---

{
  typeof src === "string" ? (
    <figure>
      <img class="mx-auto" src={src} alt={alt} />
      <figcaption class="not-prose text-center text-xs text-secondary md:text-sm">
        {alt}
      </figcaption>
    </figure>
  ) : (
    <figure>
      <Picture
        formats={["webp"]}
        fallbackFormat="jpg"
        widths={[360, 752]}
        class="mx-auto"
        {src}
        {alt}
      />
      <figcaption class="not-prose text-center text-xs text-secondary md:text-sm">
        {alt}
      </figcaption>
    </figure>
  )
}
//[...slug].astro
---
~
~
import { MdxPicture } from "~~~"
~
~
const { Content } = await post.render()
---

  <Content
    components={{
      ~
      ~
      img: MdxPicture
    }}
  />


componentsとして渡すときに特段Propsとして明示的に渡す必要がないところがポイント。

ドキュメントに書く価値のある情報のはずなのだが…