記事の管理の仕方を変えてみた

published_at
updated_at

ブログの記事管理をCMS(microCMS)に変えてみました。

今まで

今まではこのブログのソースコードと同じリポジトリにMarkdownファイル(mdx)を保管していて、記事の生成もそのファイルを利用していました。できるだけ依存するサービスを少なくして、最小限の構成でってなるとこういう管理方法も一つあると思います。

ただ、この管理方法には気になる点があります。

ブログのリポジトリはGitHubにて公開しています。なので記事もGitHubで読もうと思えば読めます。アナリティクスに影響が出るとかそういう問題もあると思うんですが、個人的に気になったのは公開が故に「記事も含めて検索ができてしまう」という点です。

ソースコードが検索対象になるのは良いと思うのですが、ただのMarkdown内の文章が検索に引っかかる可能性があるのはなんか違うなと。もちろんこんな弱小ブログは引っかかる確率のほうが低いです。ですが0ではないので、ノイズになってしまうのも申し訳ないかなと思いました。

あとはなにかGitHub内のリポジトリを引用した際に、検索結果に含まれてしまう可能性があります。それもどうなんかなーと思ってました。

CMSから記事を取得するようにした

ということで、リポジトリ内のmdxファイルをすべて削除し、CMSから記事を取得してサイトをビルドするような形に変更しました。使用したサービスはmicroCMSです。

https://microcms.io/

ヘッドレスCMSが流行ってた頃に少し使った事はあったのですが、本格的に使うのは初めてでした。TLの中でmicroCMSは良いぞと推しているのをちらほら見かけたので、少し気になって使い始めました。

結果的にはすごく使いやすく不満もなく使えています。今まで書いていたMarkdownの記事をどうするか悩んだのですが、ひとまずMarkdown用のロングテキストのフィールドを1つ作って、そこに全文入れてしまっています。

microCMSが提供しているリッチエディタに慣れていないので、今後使うかどうかはわからないですが、ひとまず記事の管理とソースコードの管理を分けることができました。

Astroでどう取得するか

以下の記事で、microCMSのエンジニアの方が最新のAstroでのチュートリアルが公開されていたので、こちらを参考にしながら実装していきました。

https://blog.microcms.io/astro5-microcms-introduction/

このブログでは少し前のAstroのバージョンが動いていました。5以降からContent Collectionというものが導入され、記事の各種プロパティをZodを用いて型の定義とバリデーションができるようになりました。ただ管理方法がガラッと変わったので、その移行作業も同時にやってしまいました。

ただこの記事と異なる点は、Markdownのテキストをそのまま配信するという点です。

せっかくHTMLにしてくれるのに、あえてMarkdownを持ってきてHTMLにパースするのもどうかとは思いますが、現状の記事が全てMarkdownで書かれており、今後もひとまずはMarkdownで書きたいと思っているのでしばらくはこの形で行きます。

microCMS上でMarkdownを書く方法も紹介されていたので、その方法で記事を書くのもありかなと思ってます!

https://blog.microcms.io/field-extension-markdown-editor/

で、AstroでMarkdownのテキストをどうしていくかについてですが、以下のような感じでやってみました。

---
// [blogId].astro
import BlogPost from '../../layouts/BlogPost.astro'
import { createHighlighter } from 'shiki'
import { getCollection } from 'astro:content'
import { createMarkdown } from 'safe-marked'

export async function getStaticPaths() {
  const posts = await getCollection('blogs')
  return posts.map((post) => ({
    params: { blogId: post.id },
    props: post
  }))
}

const post = Astro.props

const highlighter = await createHighlighter({
  themes: ['dark-plus'],
  langs: ['js', 'ts', 'tsx', 'json', 'css', 'astro', 'jsonc']
})

const markdown = createMarkdown({
  marked: {
    onInit(marked) {
      marked.use({
        renderer: {
          code(code, lang, _escaped) {
            return highlighter.codeToHtml(code, {
              lang: lang ?? 'text',
              theme: 'dark-plus'
            })
          }
        }
      })
    }
  }
})

const content = markdown(post.data.content)
---

<BlogPost post={post}>
  <section class="content mt-6" set:html={content}></section>
</BlogPost>

これはブログの記事ページです。まず marked にXSS対策のサニタイズ処理も含んだ safe-marked と、コードブロックのシンタックスハイライトをしてくれる shiki を用意します。shikiのconfigの方にはテーマと対応言語を記述しておきます。

そしてsafe-markedの createMarkdown() でrendererのコードブロック部分をオーバーライドし、shikiで作成したハイライト済みのHTMLを渡します。

最終的には content にHTMLが代入されるのでテンプレートの方に渡せば終了です。今までAstroが勝手にやってくれていた部分を、少しだけ自分でやるようになった感じですね。

最後に

本ブログはVercelにデプロイしていますが、microCMSからのWebhookでCDが走るようにもしておきました。microCMS側で用意されていたURLをペちょっと貼り付けるだけの簡単なお仕事です。やっぱり便利ですねー。

目的は達成できたのでまた気になる点があれば手を加えていきます!