← 記事一覧

このブログを作っていて踏んだ Astro の罠2つ

このブログは 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つを直した直後に公開された。