このブログはローカルのMarkdownファイルを取得してビルドしています。はじめにAstroへ移行した時はimport.meta.glob()
で取得したのですが、AstroにContents Collectionが実装されたのでそちらへ移行することにしました。
ディレクトリ構造の変更
移行前はsrc/posts
にMarkdownファイルを格納していたのですが、Contents Collectionではsrc/content
に格納する決まりなので、まずファイルを移します。移行後のディレクトリ構造は下記の通りです。
年月ディレクトリに格納するとslugがyear/month/entry
になるのですが、ここは変更可能です。
src/
content/
blog/
2024/
09/
entry.md
draft.md
config.ts
また、今回から年月ディレクトリに入っていない、blogディレクトリ直下の下書きファイルを.gitignore
で無視するようにしました。記事が完成したら、公開年月のディレクトリに移動してコミットします。
# draft
src/content/blog/*.md
型の設定
Contents Collectionでは、Zodを使ってFrontmatterをバリデーションできます。これがそのまま型になるわけですね。これを現行の運用に合わせてconfig.ts
に指定していきます。
import { z, defineCollection } from 'astro:content'
const blog = defineCollection({
type: 'content',
schema: z.object({
title: z.string(),
date: z.date(),
tags: z.array(z.string()),
draft: z.boolean().optional(),
}),
})
export const collections = {
blog,
}
記事の取得周り
ここはほぼ全て書き換えました。ついでに、並べ替えをオプションにしました。
import type { CollectionEntry } from 'astro:content'
import { getCollection } from 'astro:content'
// 記事を取得
export const getPosts = async ({
orderby,
order,
}: {
orderby?: 'date'
order?: 'ASC' | 'DESC'
} = {}): CollectionEntry<'blog'> => {
const posts = await getCollection('blog', ({ data }) => {
return import.meta.env.PROD ? data.draft !== true : true
})
if (orderby && orderby === 'date') {
const sortedPosts = posts.sort(
(a, b) => Date.parse(b.data.date) - Date.parse(a.data.date)
)
if (order === 'ASC') {
return sortedPosts.reverse()
}
return sortedPosts
}
return posts
}
getCollection()
は第2引数にfilterコールバックを指定できるため、frontmatterなどでフィルタリングできます。今回は本番環境で下書きを除外するようにしました。
詳細ページ
こちらもCollectionを使って書き換えます。各記事のオブジェクトに格納されているrender()
を使ってMarkdownからHTMLに置換された本文コンポーネントを受け取れます。また、frontmatterで指定した値はpost.data
に格納されているため、変数を書き換えます。
---
import type { CollectionEntry } from 'astro:content'
import { getPosts, getBlogParams } from '@/libs/posts'
type Props = {
post: CollectionEntry<'blog'>
}
export async function getStaticPaths() {
const posts = await getPosts()
return posts.map((post) => ({
params: getBlogParams(post),
props: {
post,
},
}))
}
const { slug } = Astro.params
const { post } = Astro.props
const { Content } = await post.render()
---
<h1>{post.data.title}</h1>
<Content/>
パーマリンク
slugを/{slug}
にするため、記事のファイルパスからパラメータを生成します。逆に、src/pages/[year]/[month]
に[...slug].astro
を格納し、frontmatterの日付から年月を取得してpath
に設定することで年月パーマリンクにもできます。
// URLパラメータを取得
export const getBlogParams = (post: CollectionEntry<'blog'>) => {
// const pubDate = post.data.date
// const year = String(pubDate.getFullYear()).padStart(4, '0')
// const month = String(pubDate.getMonth() + 1).padStart(2, '0')
const slug = (post.slug.match(/\d{4}\/\d{2}\/(.+)/) || [])[1] || post.slug
// const path = `${year}/${month}/${slug}`
return {
// year,
// month,
// path,
slug,
}
}