📅 2026-02-12
🔄 Rev: 2026/02/12 00:00
ブログを運用していると、「基本はスマホで撮った写真を高画質で載せたい(ローカル画像)」 けれど、「たまにフリー素材のURLをサクッと借りたい(外部画像)」 という時がある。
Astroの Content Collections でこれを実現しようとすると、型定義(Schema)で少し工夫が必要だった。
AIと相談して辿り着いた、「どっちも受け入れる」 最強の設定(Config)をメモしておく。
1. 悩み:Astroの画像型は厳格すぎる
通常、config.ts で image() を使うと、ローカル画像(assetsフォルダ内のファイル)しか受け付けてくれなくなる。
逆に z.string() にすると、今度はAstroの強力な画像最適化機能が使えなくなる。
「どっちも使いたい」のだ。
2. 解決策:z.union で合併させる
結論から言うと、zod の union(合併)機能を使えばいい。
「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に話しかけよう!