このブログは Astro の content collections——3つのロケール(en/ja/zh)、言語ごとに markdown 1ファイル。組み立てる途中で、記録しておく価値のある罠を2つ踏んだ。直し方が自明でないのと、最初の実戦記事にちょうどいいので。
罠1:frontmatter の slug は entry の id そのもの
同じ記事の3言語版が1つの URL を共有できるよう、各記事に slug フィールドを付けた:
lang: "en-us"
slug: "unify-mac-ios-downloads"
ビルドは通った。だが記事を一覧に出したのは中国語の index だけ——英語と日本語は「まだ記事はありません」。
glob loader は frontmatter の slug フィールドを entry の id として扱う。3ファイルが同じ slug を宣言したので、ひとつの entry に潰れ、最後に読み込まれたもの(zh-tw、アルファベット順)が勝った。残り2つは collection から消えた——エラーも警告もなし。
直し方:slug フィールドを外す。id はファイルパス(en-us/unify-mac-ios-downloads、ロケールごとに一意)に任せ、URL の slug はファイル名から導く:
const slug = post.id.replace(/^[^/]+\//, ''); // ロケールのフォルダ名を剥がす
罠2:getStaticPaths は独自のスコープで動く
id を直すと、次のビルドはもっと派手に失敗した:
slugOf is not defined
その小さな replace をルートファイル冒頭のヘルパーに切り出し、getStaticPaths の中から呼んでいた。だが getStaticPaths は特別で——Astro が抽出して隔離されたスコープで実行するため、同じ frontmatter の他のトップレベル const を見られない。ヘルパーはコンポーネントの他の部分には存在したが、その中だけ存在しなかった。
直し方:導出をインラインにするか、getStaticPaths の中で定義する:
export async function getStaticPaths() {
const posts = (await getCollection('blog')).filter((p) => p.data.lang === 'ja-jp');
return posts.map((post) => ({
params: { slug: post.id.replace(/^[^/]+\//, '') },
props: { post },
}));
}
どちらも、happy path ではビルドが緑になり、記事が2つ以上になったりヘルパーを共有したりして初めて噛みついてくるタイプの罠だ。これで分かった——あなたが今読んでいるこのブログは、この2つを直した直後に公開された。