🧊

Astroで動的なパスの画像を扱う

こういう状況がたまにあるはず。

  • .astroでリスト表示をする
  • そして各アイテムにサムネイルを出す

そんな時にどうするか。

MDXの場合

その各アイテムがContent collectionsになってて、ソースがMDXの場合。

この場合は、MDXからサムネをexportすればよくて、特に困らない。

Images | Docs https://docs.astro.build/en/guides/images/#images-in-content-collections

schemaでも縛れる。

const blogCollection = defineCollection({
  schema: ({ image }) =>
    z.object({
      title: z.string(),
      cover: image(), // 👀
      coverAlt: z.string(),
    }),
});

そうじゃない場合は?

ドキュメントを読んでてそう思ってしまった。

Content collectionsでも、カスタムloaderを使ってるとか、CSVやJSONから生成してるとかの場合に、どうすれば・・・?

まず最初に思いつくのは、/publicに置くやり方。 でもこれは避けたい。パスの扱いも困るし、なによりカオスになるのが目に見えてるから。

となると、なんとかしてAstro or Viteに画像を認識させて、そのパスを管理させる必要がある。

ということに悩んでたら、いちおうドキュメントにレシピとして書いてあった。

Dynamically import images | Docs https://docs.astro.build/en/recipes/dynamically-importing-images/

こうする

export async function getItemThumbnailSrcById(id: string) {
  const path = `/path/to/data/${id}/thumb.png`;
  const img = import.meta.glob<{ default: ImageMetadata }>(
    // NOTE: This must be a static string literal path
    "/path/to/data/*/thumb.png",
  )[path];
  if (!img) throw new Error(`Failed to load ${path}`);
  return img();
}

import.meta.glob()でViteに認識させて、画像のパスを取得する関数にしておく。

あとはこれを.astroで使うだけ。

<Image src={getItemThumbnailSrcById(item.id)} alt="" />

ImageMetadataという型により、この関数の返り値はPromise<{ default: ImageMetadata }>になる。

astro:assetsImageコンポーネントのsrcは、ImageMetadataだけでなく、Promise<{ default: ImageMetadata }>も受け付けてくれるので、リストでも気にせず使えるというわけ。

ただのimgの場合はこうはいかない。どうしてもimgを使う場合は、import.meta.glob(){ eager: true, import: "default" }などを駆使して頑張るしかない。

Imports reference | Docs https://docs.astro.build/en/guides/imports/#importmetaglob

Features | Vite https://vite.dev/guide/features.html#glob-import

Content collectionsで画像でもPDFでもなんでも管理できるようになったらいいのに。