この記事で解決すること

個人ブログが続かなくなる理由は、サイトを作れないからではなく、公開までの手順が重すぎるからということがよくあります。記事を書くたびに設定を直し、手元でビルドし、ファイルをアップロードする。これを毎回やっていると、だんだん記事を書くこと自体が面倒になります。

そこで私は、ブログの流れをできるだけ単純にしました。ローカルでは Markdown を書くだけ。GitHub に push した後は、GitHub Actions がビルドと本番公開を担当します。

こうすると、記事を書く作業と運用作業を切り離せます。ブログ更新が「毎回小さな運用作業をすること」ではなく、普通のコンテンツ制作になります。

向いているケース

  • 個人ブログ、技術メディア、軽量なドキュメントサイトを運営している
  • 記事を Markdown としてリポジトリに保存したい
  • WordPress のような重い管理画面を持ちたくない
  • 多言語コンテンツを長期的に Git で管理したい

逆に、オンライン管理画面、多人数での同時編集、複雑なレビュー承認フローが必要なら、この構成が最適とは限りません。

最終的な公開フロー

この仕組みを整えると、新しい記事を公開するときに必要な作業はほぼ次だけです。

  1. 記事のひな形を生成する
  2. Markdown を編集する
  3. ローカルで内容とレイアウトを確認する
  4. git push origin main
  5. GitHub Actions が自動で本番へデプロイする

つまり、コンテンツ制作とデプロイを分離できます。自分は記事に集中し、繰り返しの公開処理は自動化に任せます。

現在の構成

このブログでは次のスタックを使っています。

  • フレームワーク:Astro
  • コンテンツ:astro:content
  • 記事形式:Markdown
  • デプロイ:静的成果物を本番へ配置
  • Web サーバー:Nginx
  • 自動デプロイ:GitHub Actions

もともとは中国語と英語を分けた構成から始めました。

src/content/posts/zh-cn/*.md
src/content/posts/en-us/*.md

同じ記事では両方のファイルで同じ path を使い、URL は次のように分けます。

  • 中国語:/posts/<path>/
  • 英語:/en/posts/<path>/

現在はこの考え方をそのまま繁体字中国語と日本語にも広げています。

実装のポイント

1. 最初にコンテンツモデルを固定する

コンテンツモデルが途中で何度も変わると、記事を追加するたびにルーティングや変換ロジックまで直すことになります。

私は記事、固定ページ、カテゴリー、リソースをすべて astro:content に寄せました。重要なのは次の二つです。

  • 記事は path
  • 固定ページとカテゴリーは key

この形にすると、新しい記事を追加するときは Markdown ファイルを増やすだけで済みます。ルートの対応表を毎回更新する必要がありません。

2. 記事作成用のスキャフォールドを用意する

毎回過去の記事をコピーして frontmatter を書き換える方法は、記事数が増えるほど面倒になります。

そのため、プロジェクトには記事のひな形を生成するスクリプトを用意しています。

npm run new:post -- "markdown-blog-publish-workflow" "本地写 Markdown,git push 后自动上线:我的双语博客发布流程" "Write Markdown Locally, Deploy on git push: My Bilingual Blog Publishing Workflow" writing

実行すると、各言語用の下書きファイルが生成されます。下書きは最初から次の状態です。

draft: true

これが重要です。執筆途中の記事が、そのまま自動デプロイで公開される事故を防げます。

3. ローカルでは執筆とプレビューだけをする

記事を書くとき、基本的には二つだけです。

  1. Markdown を編集する
  2. npm run dev で確認する

ブラウザでは主に次を確認します。

  • トップページに記事が表示されるか
  • 各言語の記事ページが開けるか
  • カテゴリーやタグの集約が正しいか
  • タイトルと description が検索結果向けに自然か

ここまで通れば、その後に SSH で手動公開する必要はありません。

4. デプロイは GitHub Actions に任せる

この部分が、日々の運用を軽くする一番大きなポイントです。

現在の流れは次の通りです。

  1. main に push
  2. Actions が npm ci
  3. npm run build
  4. dist をアーカイブ
  5. SSH でサーバーへアップロード
  6. 新しい releases/<timestamp>-<sha> に展開
  7. current シンボリックリンクを切り替える
  8. 外部からアクセスできることを確認する

これで記事公開も通常の CI/CD と同じ扱いになります。

5. 本番サーバーではビルドしない

私が明確に決めていることの一つが、サーバーではビルドせず、静的ファイルの配信だけを担当させることです。

この方が次の点で扱いやすくなります。

  • サーバー側が安定する
  • ビルド環境を GitHub Actions に固定できる
  • ロールバックしやすい
  • 問題が起きた場所を切り分けやすい

本番側はおおよそ次の構成です。

/var/www/aitokenhacks.com/releases/<release-id>
/var/www/aitokenhacks.com/current

毎回既存ファイルを直接上書きするのではなく、新しい release を作って最後にシンボリックリンクを切り替えます。

6. 自動化には診断情報も必要

workflow が動くだけでは十分ではありません。失敗したときに原因が分かることも重要です。

そこでデプロイ処理には、次のような診断を追加しています。

  • どの Deploy Secret が不足しているか
  • SSH 接続が成功するか
  • リモートの配置先が存在するか
  • current が最終的にどの release を指しているか

これにより、赤い失敗マークだけを見るのではなく、GitHub Secrets、SSH、ディレクトリ権限、外部アクセス確認のどこで止まったかを追いやすくなります。

よくある疑問

なぜオンライン管理画面を使わないのか

今のこのサイトでは、編集画面の快適さよりも、コンテンツを蓄積し、検索流入につなげることを優先しています。

Markdown を Git で管理すると、私にとっては次の利点があります。

  • すべての履歴を Git に残せる
  • ロールバックできる
  • 差分レビューができる
  • コードとコンテンツを同じ仕組みで管理できる
  • 特定の CMS が止まっても記事データは残る

多言語記事を一つのファイルに混ぜない理由

一つの Markdown に複数言語の frontmatter と本文を入れると、すぐに保守しづらくなります。

そのため私は「言語ごとにファイルを分け、path は共通にする」方式を選びました。

  • Astro の Content Collection と相性が良い
  • 本文の管理が分かりやすい
  • URL と言語切り替えを素直に組み立てられる

自動デプロイでよく起きる失敗

実際に遭遇したのは主に次の二つです。

  • GitHub Actions の利用枠や課金状態の問題で job 自体が起動しない
  • Deploy Secrets の設定が足りず、SSH の準備段階で失敗する

そのため、私は次の順番で確認します。

  1. Actions の job が実際に開始しているか
  2. 必要な Secrets が揃っているか
  3. SSH ユーザーと鍵が有効か
  4. リモートディレクトリへ書き込めるか

まとめ

個人の技術ブログを長く運営するなら、「書くこと」と「公開すること」を分離するのがおすすめです。

執筆中はコンテンツだけに集中する。 公開時の繰り返し処理は CI/CD に任せる。

記事公開を「Markdown を書いて git push する」まで縮められると、コンテンツサイトはかなり継続しやすくなります。