
きっかけ
Zenn向けにMarkdownで技術記事を書いていて、同じ内容を自社のmicroCMS製ブログにも載せたいという場面がありました。1本だけならmicroCMSの管理画面から手作業でコピペしても大した手間ではありませんが、「今後もこの作業が増えそう」だったので、使い回せる自動投稿の仕組みを作ることにしました。
本記事では、その実装と、実際にハマったポイントをまとめます。
手作業だと何が面倒か
microCMSの本文フィールドが「リッチエディタ」型の場合、実体はHTML文字列です。Markdownの生テキスト(# 見出しのような記号付きの文字列)をそのまま貼り付けても、記号ごと文字として扱われてしまいます。
これを回避する手作業の方法は、
- VS CodeなどでMarkdownをプレビュー表示にする(見た目としてレンダリングする)
- プレビュー画面上の「見た目」をコピーする
- リッチエディタに貼り付ける
というものですが、コードブロックのシンタックスハイライトや表(テーブル)が崩れやすく、貼り付けるたびに手直しが必要でした。1回きりならこれでも良いのですが、繰り返すには向きません。
方針: Markdown→HTML変換 + Content APIへの直接投稿
microCMSは、管理画面を経由せずにコンテンツを作成・更新できるコンテンツ管理APIを提供しています。これを使えば、
- Markdownファイルを読み込む
- 本文をHTMLに変換する
- HTTPリクエストで直接コンテンツを作成する
という流れをスクリプト1本にまとめられます。貼り付け作業自体をなくしてしまう方針です。
実装
frontmatterの解析とMarkdown→HTML変換
Zennの記事はファイルの先頭にtitleやemojiなどのメタ情報(frontmatter)を持っています。ここからtitleを取り出し、本文部分はmarkedというライブラリでHTMLに変換します。
import matter from "gray-matter";
import { marked } from "marked";
const raw = fs.readFileSync(mdFilePath, "utf-8");
const { data: frontmatter, content: markdownBody } = matter(raw);
const htmlBody = await marked.parse(markdownBody);
gray-matterは、---で囲まれたYAML部分(frontmatter)と本文を分離してくれるライブラリです。これでfrontmatter.titleと、変換済みのhtmlBodyが手に入ります。
microCMSへの投稿
あとは、取得したtitleとhtmlBodyを、microCMSのコンテンツ管理APIにPOSTするだけです。
const res = await fetch(`https://xxxxx.microcms.io/api/v1/blogs`, {
method: "POST",
headers: {
"X-MICROCMS-API-KEY": process.env.MICROCMS_API_KEY!,
"Content-Type": "application/json",
},
body: JSON.stringify({
title: frontmatter.title,
content: htmlBody,
}),
});
X-MICROCMS-API-KEYヘッダーに、書き込み権限を持つAPIキーを付けてリクエストするだけで、コンテンツが作成されます。ここでのポイントは、あえて公開(publish)まで自動化しなかったことです。POSTで作成されたコンテンツはmicroCMS上では下書き状態になります。スクリプトが誤ったHTMLを投げてしまった場合に、気づかず即公開されてしまうのを避けるため、「下書きとして作成 → 人間が管理画面で見た目を確認して公開ボタンを押す」という一段階を必ず挟む設計にしました。
実行結果の分かりやすさ
投稿が終わったら、コンテンツIDと管理画面の編集URLをそのままターミナルに出力するようにしています。
console.log(`コンテンツID: ${result.id}`);
console.log(`編集画面: https://xxxxx.microcms.io/apis/blogs/contents/${result.id}`);
これにより、投稿後に「どのコンテンツが作られたんだっけ」と管理画面上で探し回らずに済むよう、コンテンツIDをすぐ確認できるようにしています(このURL自体がそのままブラウザで開けるかは環境によって差があるようなので、確実なのはIDをもとに管理画面上で該当コンテンツを探す方法です)。
ハマったポイント: POST is forbidden
実際に動かしてみたところ、最初は次のエラーで失敗しました。
投稿に失敗しました: 400 Bad Request
{"message":"POST is forbidden."}
原因は、使っていたAPIキーの権限設定でした。microCMSのAPIキーは、APIごと・メソッドごと(GET/POST/PUT/PATCH/DELETE)に権限を細かく設定でき、デフォルトでは読み取り(GET)のみが許可された状態になっていることがあります。管理画面の「APIキー」設定から、対象のAPIに対してPOST権限を明示的に有効にしたところ、正しく投稿できるようになりました。
これは裏を返せば、「書き込み権限を意図せず持っているAPIキーが存在しないか」をたまに見直す必要がある、ということでもあります。自動投稿用のキーは、必要なAPI・必要なメソッドだけに絞って権限を与えるのが安全です。
できあがったスクリプトの使い方
最終的に、こういう使い方ができるスクリプトになりました。
npx tsx scripts/publish-zenn-to-microcms.ts docs/my-article.md tech
- 環境変数
MICROCMS_API_KEYをAPIキーとして読み込む(.envファイルで管理し、リポジトリにはコミットしない) - 指定したMarkdownファイルのfrontmatterからtitleを取得
- 投稿先のカテゴリスラッグを第2引数で指定
- 本文をHTMLに変換して
contentフィールドとしてPOST - 下書きとして作成し、公開は手動
まとめ
- リッチエディタ型のフィールドにMarkdownを流し込むには、事前にHTML変換する必要がある
- 1回きりの投稿なら手作業(プレビューのコピペ)で十分だが、繰り返す前提ならAPI経由の自動化が効く
- 自動化する場合でも、「公開」の一歩手前(下書き)で人間の確認を挟むことで、誤爆による事故を防げる
- APIキーの権限不足による403/400系のエラーは定番のハマりどころなので、権限設定は必ず確認する
同じような「Markdownで書いた記事を別のCMSにも展開したい」というニーズがある方の参考になれば幸いです。


