Notion で書いた記事を Astro ブログへ自動公開するワークフロー
Notion で文章を書くのに慣れていても、ブログ側が Astro のような file-based static site だと、執筆と公開の間に小さな断絶ができます。
Notion では段落を並べ、画像を置き、構成を整理するのが楽です。
しかし公開時になると、
- 本文を repository へ移す
- frontmatter を補う
- 多言語版を作る
- build する
- push する
- deployment を待つ
という別作業が始まります。
記事数が少ないうちは問題ありませんが、継続して書くほどこの「機械的な移し替え」が面倒になります。
そこで私は、Notion は執筆入口、Git repository の Markdown は公開成果物と役割を分けました。
GitHub Actions が Notion page を取得し、Markdown へ落とし、必要な metadata と多言語版を作り、Astro の content directory に保存して build、push、production deployment までつなげます。
目的は「何でも自動にすること」ではありません。
手動で繰り返していた搬送作業を減らし、書くことと運用作業の切り替えを少なくすることです。
ブログを file-based にする理由自体は Astro vs WordPress に、repository から直接公開する流れは Markdown + GitHub Actions の公開フロー に整理しています。
この構成は、その二つの間に Notion を執筆 UI として入れる方法です。
最初から複雑な event-driven system にしなかった理由
最初は、Notion で公開した瞬間に webhook / Worker を起動し、repository を更新して deployment まで流す構成も考えました。
技術的にはきれいです。
ただ、個人ブログの第一段階で最も重要なのは speed より壊れたときにどこで止まったか分かることでした。
そのため最初はかなり保守的にしました。
- 本文は Notion page body に書く
- page property は title、description、category、tags、slug、status など metadata だけ
- 一回の workflow で一記事だけ処理
公開 = trueかつ状態 = Readyのページを対象- 既存記事更新は同じ slug を維持
- update 時は明示的に更新許可を付ける
この方が failure を分けやすいです。
記事が queue に入らないなら property を確認する。
翻訳で失敗したなら translation layer。
build で落ちたなら content schema。
deploy が始まらないなら production workflow。
チーム向け CMS ならリアルタイム trigger、多人数 review、複雑な state machine が重要になるかもしれません。
個人ブログで私が欲しかったのは「今夜書いた記事が、既存記事を壊さず、明日には安定して公開されている」ことでした。
その目的なら、単純な manual / polling 型の workflow はむしろ扱いやすいです。
全体の流れ
現在の考え方は次です。
Notion page body
-> Ready + 公開
-> GitHub Actions が Markdown を取得
-> slug / frontmatter を組み立てる
-> 多言語記事を生成
-> src/content/posts/<locale>/ に保存
-> Astro build
-> main へ push
-> production deployment
-> Notion に status / URL / commit SHA を返す
このサイトは現在、
src/content/posts/
├── zh-cn/
├── en-us/
├── zh-tw/
└── ja-jp/
の四言語構成になっています。
同じ記事は全言語で同じ path を使います。
これにより Astro 側の routing、collection、language switch、hreflang を slug ごとに揃えやすくなります。
Notion 側には本文そのものを property へ詰め込まず、page body に普通に書きます。
property は例えば、
- 名称
- 摘要
- 分類
- 標籤
- Slug
- 公開
- 状態
- 更新許可
のような metadata に限定します。
Notion は「見た目の自由な CMS」ではなく、快適な Markdown 原稿エディタとして使う
自動同期を安定させたいなら、Notion のすべての visual block をブログで再現しようとしない方が楽です。
私は Notion を「書きやすい Markdown editor」に近い感覚で使っています。
- 見出しは正しい heading level
- 本文は普通の paragraph
- command / config は fenced code block
- image は関連する段落の近く
- 重要な external link や video は単独行
- 複雑な column layout や装飾カードへ依存しない
Notion で見た目がきれいでも、Markdown export したときに同じ構造になるとは限りません。
そのため最初から「export しやすい書き方」に寄せておく方が、自動化全体は安定します。
例えば次の程度で十分です。
# タイトル
最初の 2〜3 段落で、問題と選択理由を説明する。
## 重要なポイント
本文を続ける。
```bash
pnpm build
node scripts/notion-auto-publish.mjs process
その後にトラブルシュート、制約、復旧方法を書く。
https://example.com/some-reference
つまり「Notion の visual page を Astro に完全変換する」のではなく、Notion で既にきれいな source Markdown に近い原稿を書く考え方です。
## スクリプトの責任を分ける
現在の script entry は主に次です。
```bash
node scripts/notion-auto-publish.mjs process
node scripts/notion-auto-publish.mjs finalize-success
node scripts/notion-auto-publish.mjs finalize-failure
process は、
- 対象 Notion page を選ぶ
- 本文 Markdown を取得
- metadata を検証
- slug を決める
- 記事ファイルを作る
ところまで担当します。
その後 build、commit / push、deploy trigger が続きます。
Notion への success / failure 書き戻しを別 command にしているのは、巨大な一つの処理にせず、どこまで成功したかを分けやすくするためです。
自動 publishing で特に危険なのは、翻訳より既存記事の上書き判定です。
新規記事のつもりだったのに自動 slug が既存記事と衝突し、古い記事を更新してしまう方が怖いからです。
そのため私は次を保守的にしています。
- 新規記事はできるだけ unique slug を明示
- repository に同 slug があれば default で停止
- 既存記事更新は元の slug を維持
- 明示的な update permission があるときだけ overwrite
自動化は「賢く推測して進める」より、曖昧なら止まる方がブログでは安全です。
AI / Translation layer を信用しすぎない
この公開 pipeline では、AI は便利な加工レイヤーですが、最終的な整合性の責任は持たせません。
OpenAI-compatible gateway は実装によって挙動が違います。
例えば過去には、
- HTTP 200 だが本文が空
- non-streaming では空だが streaming では返る
- schema を渡しても field name が期待と違う
といったケースがありました。
そのため translation / metadata generation には fallback が必要です。
ただし、より重要なのは AI の出力をそのまま production truth にしないことです。
最終的な硬い境界は、
- content schema
- slug collision protection
- build
- Git history
- deployment validation
に残します。
モデルは title、description、translation を助けますが、repository のルールを超えて勝手に公開できる設計にはしません。
Dry run を先に使う
特定の Notion page を試すとき、いきなり本番 publish しない方が楽です。
workflow へ page_id を指定し、dry_run を使います。
この状態では、
- Notion page を取得
- 記事ファイルを生成
- translation / localization
- Astro build
までは実行しますが、main へ push せず、production state も変更しません。
この一段があるだけで、Notion property、Markdown export、translation、content schema の問題を deployment 前に分けられます。
実運用で守っている最小ルール
全部覚えなくても、次だけ固定するとかなり安全です。
- 本文は Notion page body に書く
- metadata は property
- 新規記事は unique slug を明示する
- 既存記事更新では slug を変えない
- update は明示的に許可する
- source language を公開可能な状態まで整えてから localization する
- error 時は最後の
exit 1ではなく、最初に失敗した step を見る - build を deployment の gate にする
この workflow は「最も高度な CMS」を作るためのものではありません。
Notion の書きやすさ、Astro の file-based content、GitHub Actions の反復作業自動化をつなぎ、日常的に使える程度まで摩擦を下げるための仕組みです。
私にとっては、管理画面にボタンを増やすより、この方が長く使いやすいです。