[Astro] z.enum で型安全なカテゴリ設計にする
Techこのブログはカテゴリを5つ(tech / marketing / office / music / life)に固定しています。記事の frontmatter に手で書く以上、タイポや想定外カテゴリが混ざるのが怖いところ。そこを Content Collections の Zod スキーマ z.enum でビルド時に弾くようにしました。
スキーマで enum を切る
content.config.ts の category を z.enum([...]) にします。
// src/content.config.ts
import { defineCollection } from 'astro:content';
import { glob } from 'astro/loaders';
import { z } from 'astro/zod';
const blog = defineCollection({
loader: glob({ pattern: '**/*.md', base: './src/content/blog' }),
schema: z.object({
title: z.string(),
category: z.enum(['tech', 'marketing', 'office', 'music', 'life']),
// ...
}),
});
export const collections = { blog };
これで、例えば frontmatter に category: tecn(タイポ)と書くと、ビルドや astro check でエラーになります。
Invalid enum value. Expected 'tech' | 'marketing' | 'office' | 'music' | 'life', received 'tecn'
実行時に気づくのではなく、ビルドが通らないので確実です。
カテゴリ定義を1か所に集約する
ラベルやアイコンなどの付随情報も、site.ts にまとめて持っておくと取り回しが良いです。keyof typeof で型も自動生成します。
// src/data/site.ts
export const CATEGORIES = {
tech: { label: 'Tech', icon: 'code' },
marketing: { label: 'Marketing', icon: 'chart' },
office: { label: 'Office', icon: 'book' },
music: { label: 'Music', icon: 'music' },
life: { label: 'Life', icon: 'road' },
} as const;
export type CategoryKey = keyof typeof CATEGORIES;
CategoryKey は 'tech' | 'marketing' | 'office' | 'music' | 'life' というユニオン型になります。コンポーネントの Props をこれで縛れば、カテゴリを受け取る箇所すべてが型安全に。
---
import type { CategoryKey } from '@/data/site';
interface Props { category: CategoryKey; }
const { category } = Astro.props;
const { label } = CATEGORIES[category]; // 補完も効く
---
ハマりどころ:2か所のリストは手で揃える
z.enum([...])(スキーマ)と CATEGORIES(定義)で、カテゴリのリストが2か所になります。本来は1か所にしたいところですが、z.enum は引数にリテラルの配列を要求するため、Object.keys(CATEGORIES) をそのまま渡すと型が string[] に落ちて enum になりません。
割り切って2か所に書き、「片方を変えたらもう片方も」と決めておくのが現実的でした。どうしても1か所にしたいなら、CATEGORIES のキーから z.enum(Object.keys(CATEGORIES) as [string, ...string[]]) のようにキャストする手もありますが、可読性とのトレードオフです。
メモ
z.enumで frontmatter のカテゴリをビルド時に検証できる(タイポ・未定義カテゴリを弾ける)keyof typeofでカテゴリ定義からユニオン型を自動生成、Props もそれで縛る- スキーマと定義の2重管理は割り切る(or キャストで1本化)
カテゴリのような「決め打ちの集合」は、enum 化しておくと後々の事故が減って安心でした。
Astro, TypeScript