【Astro × microCMS】特定の値を持つ投稿をビルドから外した時の苦労話

さきがけデジタルの越高です。今年もよろしくお願いします。
「そろそろ自社サイト立ち上げ時のAstroの話書きたいな~」と思っておりました。

いろいろぶつかった壁はあるのですが、その中でも苦戦した「特定のページをビルドから除外したかった」話を書きたいと思います。

前置き:サイト制作とAstro学習について

前置きとして、自社サイトは microCMS と Astro を使って構築しています。
また、VSCode を使って作業しています。
詳しいサイトの作り方は、microCMS さんの記事で紹介していますので、こちらが参考になります。

サイト制作にあたっては、Udemyの たにぐちまこと さんの「ちゃんと学ぶ、Astro」を購読し、挑みました。

Udemyで習ったこととをトレースしながら、なんとか形にしていきました。

本題:サイトの仕組みと「やりたかったこと」

自社サイトを構築する際、microCMS上で(いろいろあって)「ブログ」と「お知らせ」を、1つのコンテンツとして登録することになりました。
これらは、まとめて「投稿記事」というコンテンツ(API)に入っています。

APIを呼び出した時に、「ブログ」という値を持っている場合はビルド先を「blog」以下に、「お知らせ」という値を持っている場合は、ビルド先を「news」以下にするように設定しました。

これは学習通り、フォルダ構成を下記のようにすれば達成できます。

pages
 |-blog
  |--[blogId].astro
  |--index.astro
 |-news
  |--[newsId].astro
  |--index.astro

ただし、ストレートに呼び出すと、ここで問題が発生。
例えば、投稿記事内に、ブログ記事A、ブログ記事B、お知らせ記事Cと、
3つ登録した場合にビルドするとどうなったか。

【生成されたページ(イメージ)】
・/blog/ブログ記事A
・/blog/ブログ記事B
・/blog/お知らせ記事C
・/news/ブログ記事A
・/news/ブログ記事B
・/news/お知らせ記事C

このように「ブログ」「お知らせ記事」の両方でページが生成される問題が発生。
URLは違うけど、同じ内容のページが2つあることになります。
「ブログ」と指定された場合は、その記事だけがblog以下に生成されるようにしたいのです。

【実現したい形(イメージ)】
・/blog/ブログ記事A
・/blog/ブログ記事B
・/news/お知らせ記事C

ちなみに、普通にAPIもう1個足せば回避できるのでは…と思いつつ。
ちょっと違った方向でアプローチしていきます。

調査と実際にやったこと

まずは、Astro初心者なので、キーワードがわからない。
調べ方からわからないので、チャットGPTに聞いてみました。3つの回答が返ってきました。

【チャットGPT】

1. getStaticPathsでフィルタリングする
2. astro:build:doneフックを使用する
3. 動的ルーティングで除外する
まとめ:このように、getStaticPathsで事前にフィルタリングする方法が一番簡単で、一般的です。

初心者ゆえ、2つ目がよくわからないので、1つ目と3つ目あたりで考えていきます。

【チャットGPT】

動的ルーティングで特定のページを除外するには、[slug].astroのように動的ルートが設定されている場合に、URLパラメータ(例えばslug)に基づいてページを条件分岐で制御します。特定の条件に当てはまる場合、そのページを404エラーやリダイレクトにすることで事実上除外できます。

// 例: src/pages/blog/[slug].astro
export async function getStaticPaths() {
  const posts = await getAllPosts();
  
  // 除外するページをフィルタリング
  const filteredPosts = posts.filter(post => post.slug !== "exclude-this-post");
  return filteredPosts.map(post => ({
    params: { slug: post.slug },
    props: { post }
  }));
}

やれそうだけど、実際に書いているコードと差異があるため、このままコピペして力業実装では無理そう。しかも、何を書いているのか少し分からないところも…。まずは、各値を紐解いていく方が良さそうです。

このあたりで「なるほど」と思った方は、一番下の結論までスクロールしてください…

初心者なので、考えたことと、紐解いていく過程も書いていきます。

値を紐解く

★ここに書くのは、blogの場合です。newsでも同様に行っています。

console.logを使うと、ローカル環境でも VSCode上のターミナルに値が表示されます。

//見たかった場所
export async function getStaticPaths() {
	const response = await getBlogs({ fields:["id"]})
	console.log(response); //★まずはここを開けてみる☆
}

【取得結果】

15:15:03 [watch] src/pages/blog/[blogId].astro
{
  contents: [
    { id: 'furusatodenpo-oiwai-2024' },
    { id: 'work-with-notionapi-and-powershell' },
    { id: 'website-renewal-2024' }
  ],
  totalCount: 3,
  offset: 0,
  limit: 10
}

…なるほど、「{ fields:["id"]}」を引っ張ると(当然ながら)こうなるということか。

このブログの場合は「category」として、「ブログ」と「お知らせ」をコンボボックスで1つだけ選択するようにしているので、これを引っ張ってくればよいと思われます。

export async function getStaticPaths() {
	const response = await getBlogs({ fields:["id","category"]}) //★ここが引っ張ってくる値
	const filteredPosts = response.contents.filter(post => post.category[0] === 'ブログ');
}

「category」の値は、配列として持ってくるので「post.category[0]」と指定。
これが「'ブログ'」であれば、ビルドするようにすればいいんじゃないかなあ…と。

結論

//pages/blog/[blogId].astro
export async function getStaticPaths() {
	const response = await getBlogs({ fields:["id","category"]})
	const filteredPosts = response.contents.filter(post => post.category[0] === 'ブログ');
	return filteredPosts.map((content)=>({
		params: {
			blogId: content.id
		}
	}));
}

const filteredPosts = response.contents.filter(post => post.category[0] === 'ブログ');
この辺でフィルタかけて、その結果だけをブログのidとしよう、と考えたわけです。

これでうまくいきました~~。

以上です。おわり~!

Astroネタはあるのですが、
正直なところ「これってホントに正しかった…?」と、ちょっとだけ不安になることもあります。
これからも動くものを作っていきたいなあと思っています。

越髙佑芽さんのプロフィール画像
越髙佑芽

12月に、ハリポタのスタジオツアー東京に行ってきました🧙🏻‍♂️<ステューピファイ!

大学院では新聞の自動切り抜きシステムを研究。入社後はプログラム開発を担当。サメ映画とポケモンが好き。

こんな記事もあります

お問い合わせ

ウェブ制作や「ふるさと電報」に関するお問い合わせ、ご相談などお気軽にご連絡ください。お見積りは無料で作成いたします。