Romira's develop blog

ヘッドレスCMS Newtがサービス終了するので自作ブログ管理システムにRustで移行した

Cover image of ヘッドレスCMS Newtがサービス終了するので自作ブログ管理システムにRustで移行した

はじめに

2025年5月、ヘッドレスCMSのNewtがサービスの終了予定を発表しました。2026年11月24日をもって全サービスが停止される予定です。

本ブログはNewtで記事を管理していたため移行先が必要になりましたが、microCMS等への乗り換えではなく自作を選びました。AI時代の今なら、ブログ管理システムとエディターくらい作れるだろうという判断です。

本ブログはフロントエンドも含めて全てRustで構築しています(フレームワークはLeptos、技術構成の詳細は過去の記事をご覧ください)。

【Rust】ブログサイトをyewからLeptosで再実装しました

本記事では、移行にあたってのインフラ構成、エディター実装、CDNキャッシュ戦略、データ移行について紹介します。

インフラ構成

Newtの代わりに、記事データはPostgreSQLで管理することにしました。セッション管理にはValkey(Redis互換)を利用しています。

Pasted image 20260215152232.png

画像管理

Newtには画像をGCSにアップロードして管理する機能があり、そのBucketにイメージオプティマイザサービスのimgixを接続していました。移行後もこのBucketとimgixをそのまま継続利用し、アプリケーションから直接GCSにアップロードする形に変更しています。

認証

自作のエディター画面を設けたので、認証が必要になりました。管理画面はGoogleログインで保護しています。Google Workspaceを使っていないので組織内アカウント制限ができず、アプリケーション側でメールアドレスをチェックしています。

コスト

Newtはフリー枠で使っていました。移行後のPostgreSQL・ValkeyもOCI Always Free枠内で動かしているので、コスト0のままです。

マークダウンエディター

Newtがマークダウン記法だったので、既存の記事をコピペで移行できるようにマークダウンエディターを実装しました。DBにはマークダウンのまま保存して、表示時にHTMLへ変換しています。

マークダウンパーサーの選定

マークダウンからHTMLへの変換クレートを選定するにあたり、Rustで利用可能な主要クレートを比較しました。

comrak pulldown-cmark markdown-rs
CommonMark準拠 652/652 (100%) 100%目標 100% (2,300+テスト)
GFM準拠 670/670 (100%) table, strikethrough, tasklist, footnotes(オプション) table, strikethrough, tasklist, autolink, footnotes
追加拡張 superscript, description lists, math, wikilinks, emoji なし MDX, math, frontmatter
アーキテクチャ AST構築(typed-arena) プルパーサー(イテレータ) ステートマシン + AST(mdast)
速度 正確性優先 高速、メモリ効率◎、SIMD対応 -
WASM comrak-wasm あり no_std対応 言及なし
採用実績 crates.io, docs.rs, GitLab 広く利用 -

結果としてcomrakを採用しました。決め手はGFM 100%準拠です。comrakはcmark-gfm(GitHubの公式Cパーサー)のRustポートで、GFM仕様に対する670/670のテストをパスしています。crates.io・docs.rs・GitLabで使われている実績も安心材料でした。

pulldown-cmarkもGFMの一部拡張をオプションでサポートしていますが、仕様全体への準拠は保証していません。速度面ではpulldown-cmarkに分がありますが、ブログ記事程度の文量であれば差は体感できません。markdown-rsはMDXやmath対応が魅力的でしたが、WASMでの利用実績が確認できなかったため今回は見送りました。

comrakでは以下のGFM拡張を有効にしています。

  • strikethrough(打ち消し線)
  • table(テーブル)
  • autolink(URL自動リンク)
  • tasklist(チェックリスト)

プレビュー

エディターには3つのビューモードがあります。Split(左右並列表示)、Editor(エディターのみ)、Preview(プレビューのみ)。

マークダウンからHTMLへの変換はクライアントサイド(WASM)で実行しています。comrakはWASMバインディング(comrak-wasm)が提供されており、サーバー往復なしにブラウザ上で変換が完結します。WASMはネイティブコードに近い速度が出ると言われており、実際ブログ記事程度の文量なら入力に追従してプレビューがリアルタイムに更新されます。エディターとプレビューのスクロール同期も実装しています。

画像アップロード

画像管理にはNewtで使っていたGCSバケットとimgixをそのまま使い回しています。サーバーで署名付きURLだけ発行して、実ファイルはブラウザからGCSへ直送しています。

ドラッグ&ドロップかファイル選択で画像を挿入すると、カーソル位置にimgixのレスポンシブ対応<img>タグが自動挿入されます。imgixのURL変換パラメータで800px・1200px・1920pxの3段階のsrcsetを生成しています。

<img
  src="https://example.imgix.net/photo.jpg"
  srcset="https://example.imgix.net/photo.jpg?w=800&auto=format&q=75 800w,
          https://example.imgix.net/photo.jpg?w=1200&auto=format&q=75 1200w,
          https://example.imgix.net/photo.jpg?w=1920&auto=format&q=75 1920w"
  sizes="(max-width: 800px) 800px, (max-width: 1200px) 1200px, 1920px"
/>

auto=formatでブラウザに応じてWebPやAVIFを自動選択し、q=75で品質を調整しています。元画像を1枚アップロードすれば、imgixのCDN側で画面幅に応じた画像を生成・配信してくれます。管理画面のサムネイルはw=200&h=200&fit=cropで正方形にクロップするなど、用途で使い分けています。記事へのタグ設定も実装しており、テキスト入力で既存の候補から選択でき、未登録のタグは保存時に自動作成されます。

Cloudflare Cache-Tag

本ブログはCloudflare CDNでキャッシュしているので、記事を更新したらキャッシュパージが必要です。Newt時代はWebhookをトリガーにGitHub Actions経由でCloudflare APIを叩いていました。

Freeプランの制約

本ブログの開発当初(2024年頃)、Cloudflare Freeプランで使えるパージ方法はURL指定の単一パージか全体パージの2択でした。全体パージは関係ない記事のキャッシュまで飛ぶので無駄ですし、URL単一パージは対象ページを1つずつ列挙しないといけない。記事ページだけでなくトップページやsitemapも対象になるので、地味に面倒でした。

Cache-TagのFreeプラン開放

ところが2025年4月、Cloudflareが「Instant Purge for All」としてCache-Tag(Purge by Tag)を含む全パージ方法をFreeプランに開放しました。Cache-Tagは元々2015年にEnterprise専用として導入された機能で、約10年間上位プラン限定だったものです。

レスポンスヘッダーにCache-Tag: tag1,tag2を付けておくと、あとからタグ指定でまとめてパージできます。URL一覧を組み立てるコードが要らなくなります。

実装

Cache-TagがFreeプランで使えるようになっていたので、今回の移行で採用しました。各ページのレスポンスにCache-Tagを付与しています。

  • トップページ: top-page
  • 記事ページ: article:{slug}
  • sitemap: sitemap

記事を更新したらarticle:{slug}top-pageをパージして、その記事ページとトップページのキャッシュだけ消します。Freeプランのレート制限は5リクエスト/分ですが、個人ブログの更新頻度なら問題ないと判断しました。

Newtからの移行戦略

データ移行

既存の記事はNewtからエクスポートしてPostgreSQLに投入しました。Newtがマークダウンで記事を管理していたので、本文データはそのままDBに入れられました。マークダウンエディターを自作した理由の一つもここにあって、既存記事をコピペで移行できる形式を維持したかったからです。

リダイレクト

Newt時代の記事URLと新しいURLが異なるので、旧URLからのリダイレクトが必要でした。対象は4記事だけだったので、Newt記事IDと新DB記事slugのマッピングをアプリ内にベタ書きしています。SSRは301リダイレクト、SPA遷移はreplaceで処理しています。301にしておけば検索エンジンのインデックスが新URLに引き継がれます。

段階的リリース

移行はfeatures=localというCookieトグルを使い、旧Newt表示と新DB表示を切り替えながら段階的にリリースしました。個人ブログレベルなので一気にリリースしても問題はなかったのですが、Rustブログとしてそこそこ検索に引っかかるようになっていたので、切り替え時の不具合でインデックスを失いたくなかったためです。動作確認が済んだ段階でトグルを撤去し、DB記事をデフォルト表示に切り替えました。

Newtの管理画面と、移行後の自作管理画面の比較です。

Before: Newt CMS管理画面

Pasted image 20260215145645.png

After: 自作管理画面

Pasted image 20260215145731.png

開発規模とAIとの協業

今回の移行の規模感です。

  • 開発期間: 約20日間(2026年1月25日 〜 2月14日)
  • PR数: 31件マージ(#170〜#202)
  • コミット数: 88コミット
  • コード差分: Rustコード +4,518行 / -608行(純増 +3,910行)、SCSS +583行を含め全体で90ファイル変更
  • 1日あたり約1.6PR、4.4コミットのペース

主な追加領域の内訳です。

領域 追加行数
管理画面テスト +605行
画像管理 +415行
カテゴリ管理 +218行
CDNキャッシュパージ +160行
認証 +101行
SEO +91行

開発はAIと進めました。フロントエンドRustというニッチな領域でAIがどこまで使えるか不安でしたが、Leptosのコードは普通に書けました。コンパイラのエラーメッセージが丁寧なので、AIに投げ返すとだいたい直してくれます。型が厳格で「コンパイルが通れば動く」言語なので、AIが生成したコードの品質をコンパイラが担保してくれる面があります。動的型付けの言語なら実行時に初めて気づくような型の不一致やnullアクセスも、Rustではコンパイル時に弾かれます。もちろんロジックのバグはコンパイラでは防げませんが、型レベルの不整合はAI任せでも安心できました。

20日間でRust約4,500行のフルスタック自作ブログ管理システムを1人+AIで開発できたのは、なかなかいい体験でした。

まとめ

ヘッドレスCMSのサービス終了をきっかけに、自作ブログ管理システムへ移行しました。AIを使って、約20日間・Rust約4,500行で実現できています。

自作のメリットは自由度です。Cache-Tagパージ、WASMプレビュー、署名付きURLでの画像アップロード。欲しい機能を好きなタイミングで追加できます。一方、メンテナンスは全部自分持ちです。ヘッドレスCMSなら勝手にやってくれていた可用性やセキュリティアップデートを、自前で面倒見ないといけません。

コストはOCI Always Free枠で0円を維持できています。メンテナンスは自分持ちになりますが、今の時代AIに聞けばだいたいなんとかなるので、そこまで心配していません。個人開発者で技術的な自由度を求めるなら、AI時代の今は自作も十分ありだと思います。