Site Icon 古老と生成AI
📅 2026-02-12 🔄 Rev: 2026/02/12 00:00

ブログを運用していると、「基本はスマホで撮った写真を高画質で載せたい(ローカル画像)」 けれど、「たまにフリー素材のURLをサクッと借りたい(外部画像)」 という時がある。

Astroの Content Collections でこれを実現しようとすると、型定義(Schema)で少し工夫が必要だった。 AIと相談して辿り着いた、「どっちも受け入れる」 最強の設定(Config)をメモしておく。

1. 悩み:Astroの画像型は厳格すぎる

通常、config.tsimage() を使うと、ローカル画像(assetsフォルダ内のファイル)しか受け付けてくれなくなる。 逆に z.string() にすると、今度はAstroの強力な画像最適化機能が使えなくなる。

「どっちも使いたい」のだ。

2. 解決策:z.union で合併させる

結論から言うと、zodunion(合併)機能を使えばいい。 「Aパターン(ローカル画像)」または「Bパターン(外部URL)」のどちらかならOK、という門番を作るイメージだ。

src/content/config.ts

import { defineCollection, z } from 'astro:content';

const postsCollection = defineCollection({
  // ({ image }) を引数で受け取るのを忘れずに
  schema: ({ image }) =>
    z.object({
      title: z.string(),
      // 日付は柔軟に解釈させる (coerce)
      pubDate: z.coerce.date(),

      // 👇 ここがポイント!
      image: z
        .union([
          // パターンA: ローカル画像 (../../assets/photo.jpg)
          // Astroが自動で画像データに変換して最適化してくれる
          image(),

          // パターンB: 外部画像 (https://...)
          // 文字列のURLとしてそのまま扱う
          z.object({
            url: z.string(),
            alt: z.string().default('記事サムネイル'),
          }),
        ])
        .optional(), // 画像がない記事も許容する

      description: z.string(),
      category: z.string(),
      tags: z.array(z.string()),
    }),
});

3. 表示側の工夫(.astroファイル)

データを受け取る側(記事リストや詳細ページ)では、「今入っているのはどっちのデータ?」 を判断してあげる必要がある。

これには in 演算子が便利だ。

{/_ 画像がある場合のみ表示 _/}
{post.data.image && (
<img
src={
// 'src' プロパティを持っていたらローカル画像(Astro処理済み)
'src' in post.data.image
? post.data.image.src
: post.data.image.url // なければ外部URL
}
alt="Thumbnail"
class="w-full h-full object-cover"
/>
)}

まとめ

これで、Markdown側では何も気にせず、

image: "../../assets/my-photo.jpg"

image: {url: "https://example.com/photo.jpg", alt: "..."}

のどちらを書いてもエラーにならず、かつローカル画像の場合はAstroの高速化恩恵を受けられるようになった。 「迷ったら z.union」。これが今回の教訓だ。

執筆協力:Gemini / 編集・監修:古老

💬 電脳古老&変AIへのコメント

記事の感想・質問・雑談をどうぞ(200文字くらいまで推奨)

まだコメントはありません。一番乗りで古老とAIに話しかけよう!

※入力した内容はAI(Gemini)に送信され、自動返信が生成されます。個人情報は入力しないでください。