このブログは自作CMSで動かしています。記事編集だけでなく、画像管理、アイキャッチ生成、SEO設定、XMLサイトマップ、アクセス集計、二要素認証まで実装しました。この記事では、実際にブログを運営しながら追加してきた機能をまとめます。これからブログCMSを自作したい人の機能設計の参考にもなると思います。
こんなめんどっちいことをすることになったきっかけなどは以下の過去記事からどうぞ。
ブログが形になってきたのでここで一度メリットデメリットをまとめました。ブログが形になってきたのでここで一度メリットデメリットをまとめました。記事を読む
GoogleのBloggerなのにGoogleに出ない。じゃあ自分で作ることにしたしばらくGoogle Bloggerを使っていました。無料で使えて、HTMLやCSSもかなり自由に変更できます。実際に使ってみると思っていたよりカスタマイズできて、「無料でこれなら十分じゃない?」と思っていました。見た目も少しずつ自分好みに変えて、これでしばらくブログを書いていこうと思っていました。 けど、いくら待って記事を読む最初は「記事を書いて公開できればいい」くらいだったのですが、実際に使うと、画像をもう少し簡単に入れたい、表を見やすくしたい、アイキャッチを毎回別のソフトで作るのが面倒、サイトマップも忘れず更新したい、となって、少しずつ機能が増えました。
自分でも何を入れたか分からなくなりそうなので、今の時点でできることを一度全部書いておきます。きれいな製品紹介というより、実際に作った際のメモとして置いておきます。
記事を書く画面は、普通の文書作成に近くした
本文はTiptapを使ったビジュアルエディタです。HTMLを直接書かなくても、その場で見ながら文章を作れます。

入っている書式は、本文、H2・H3などの見出し、太字、斜体、取り消し線、下線、マーカー、文字サイズ、文字色、左・中央・右揃え、箇条書き、番号付きリスト、引用、リンク、区切り線、短いコード、コードブロックなどです。コードブロックはPHP、JavaScript、TypeScript、HTML、CSS、Python、SQL、Bash、JSON、Markdown、YAMLなどを選べます。言語を指定しない場合の自動判定も入れています。
ChatGPTなどで下書きしたMarkdownを貼り付けても、見出しやリスト、表、引用、コードブロックを編集できる形に変換します。HTMLを貼り付けた場合は、そのまま危険なコードを保存するのではなく、記事で使える要素に整理してから取り込みます。普通の文章は普通の段落として貼れます。
未保存の状態でもプレビューボタンを押すと、公開ページに近い見え方を別タブで確認できます。本文だけでなく、タイトル、カテゴリー、タグ、アイキャッチ、目次も含めたプレビューです。プレビュー自体は検索に出ないようにしています。
表や画像の編集は操作性にこだわった
ブログの表は、PCでは読めてもスマートフォンで崩れたり、全部同じ見た目で内容に合わなかったりします。ここは後から細かくしました。

エディタ上で表を入れた後に、行と列の追加、先頭行を見出しにする、先頭列を見出しにする、表の削除ができます。選択したセルだけ左・中央・右揃えにすることもできます。
表ごとに次の設定を持たせています。
標準、しま模様、罫線、最小限、強調のデザイン
緑、青、オレンジ、無彩色の配色
コンパクト、標準、ゆったりの余白
本文幅いっぱい、内容に合わせる、の幅
先頭列の強調
これらは記事全体ではなく、選んでいる表だけに反映されます。スマートフォンに収まらない表は、ページ全体を横に広げるのではなく、表の部分だけ横スクロールします。
画像は貼り付けて終わりではなく、後から管理できる
画像はJPEG、PNG、WebP、AVIFに対応しています。画像ボタンから選ぶ方法のほか、本文へスクリーンショットをそのまま貼り付けることもできます。アイキャッチ欄にも画像を貼り付けられます。

アップロード時には、本当に画像として読めるか、MIMEタイプ、容量、縦横サイズを確認します。上限は10MBです。保存名はランダムな名前に変えて、年月ごとのフォルダーへ置きます。
大きな画像には、元画像に加えて480px、960px、1600pxの縮小版を作ります。公開ページでは画面に合う画像をブラウザが選びます。小さなスマートフォンで、必要もないのに大きな元画像を毎回読むのを避けるためです。
本文へ入れた後も、画像ごとにいろいろ変えられます。
alt(代替テキスト)
画像の下に出すキャプション
出典URL
左、中央、右の配置
原寸、25%、50%、75%、本文幅
25%から100%まで5%刻みの表示幅
本文画像のalt、キャプション、出典、幅、配置は、メディア台帳の共通情報をそのまま表示するのではなく、本文内のその画像ノードに保存します。同じ画像を別の場所で使う場合でも、記事中の見せ方まで強制的に同じにはしないためです。
文字入りアイキャッチを管理画面の中で作れる
これは自分でもよく使う機能です。記事タイトルを入れた1200×630pxのアイキャッチを、記事編集画面から作れます。

背景は、アイキャッチ欄に設定した画像を使う、新しい画像を選ぶ、無地にする、という使い方ができます。そこへ記事タイトルを自動で入れます。文字は手直しでき、改行、文字色、背景色、文字サイズ、上・中央・下の位置、左・中央・右揃え、背景を暗くする割合を調整できます。
文字が長い場合は、枠に収まる範囲で文字サイズを小さくします。それでも収まらない場合は、そのまま見切れた画像を保存せず、文章や改行を減らすように表示します。完成した画像はCanvasでPNGにして、通常のメディアと同じように保存し、そのまま記事のアイキャッチに設定します。
生成時の文字、色、位置、背景画像などの設定も保存してあるので、後でアイキャッチを開き直して調整できます。いわゆる画像生成AIではなく、背景画像と文字を組み合わせて、毎回同じサイズの画像を作る機能です。その分、記事タイトル入りの画像を手早く作る用途では安定しています。
メディア管理では「どこで使っているか」まで見る
画像が増えると、消してよい画像なのか分からなくなります。それが嫌だったので、メディア一覧には「使用中」「未使用」の表示を付けました。使用中だけ、未使用だけで絞り込むこともできます。
使用先として調べているのは、現在の記事本文、記事のアイキャッチ、生成アイキャッチの背景、ロゴ、既定OG画像、プロフィール画像などです。どの記事で使っているかもリンクで表示します。記事の過去の更新履歴でだけ使っている画像も別に表示します。
現在使っている画像は削除できません。生成アイキャッチの元画像も使用中として扱います。未使用なら、元画像と自動作成した縮小版をまとめて削除します。ただし過去版で使っていた場合は、削除すると履歴を復元したときに画像が欠けることを先に警告します。
alt、キャプション、出典URLはメディア台帳の情報としてメディア一覧からも直せます。本文内に挿入済みの画像は、その使用場所に保存された設定を記事編集画面で直します。未使用画像を続けて整理するときに、更新や削除のたびに絞り込みが解除されないようにもしました。かなり細かい部分ですが、実際に何枚も整理するとここが気になります。
見出しを書けば目次は自動で作る
記事本文にH2からH4の見出しが2つ以上あると、公開ページに目次を自動で出します。見出しを別の欄へもう一度入力する必要はありません。

H2、H3、H4の階層を読んで、1、1.1、1.1.1のような番号を付けます。目次の項目を押すと、その見出しまで移動します。目次は最初は開いた状態ですが、読者が表示・非表示を切り替えられます。この記事の目次も、公開すれば本文の見出しから作られるはずです。
保存、公開、やり直しの機能
記事の状態は、下書き、予約公開、公開、アーカイブです。日時を指定して予約公開できます。下書きとアーカイブは公開一覧、関連記事、RSS、サイトマップには出さず、URLを開いても通常は表示しません。
一度保存した記事は編集途中に自動保存します。保存時には本文の構造データから公開用HTMLをサーバー側で作り直します。本文の最初の文章から、一覧で使う抜粋も自動で作ります。
記事を更新すると、変更前の内容を更新履歴へ残します。同じ内容を何度も重複して残さず、最新20世代を保持します。履歴から以前の版へ戻すことも、不要な履歴だけ消すこともできます。復元前の現在版も履歴に残します。
ほかに、記事の複製、ゴミ箱への移動、ゴミ箱からの復元があります。複製した記事は、間違って同じURLで公開しないように、新しいslugの下書き、noindexの状態から始めます。
記事一覧では、タイトルやslugの検索、公開状態、カテゴリー、タグで絞り込めます。今日、7日、累計の閲覧数も記事ごとに見られます。
ページはデータベースから動的に作っている
記事ごとにHTMLファイルを手作業で増やしているわけではありません。RDBから記事、カテゴリー、タグ、画像、サイト設定を読み、アクセスされた時点の内容でページを作ります。
動的なのは記事ページだけでなく、トップページ、記事一覧、カテゴリー・タグ別ページ、カテゴリー一覧、タグ一覧、月別アーカイブ、サイト内検索も同じです。記事を公開すれば一覧に出て、カテゴリーやタグのページにも反映されます。
ただし、ブラウザでJavaScriptが動くまで本文が出ない作りにはしていません。サーバーから、本文やリンクを含むHTMLを返します。JavaScriptが使えない状態でも記事本文を読めて、検索エンジンも通常のリンクをたどれます。
記事内にこのブログの別記事URLだけを書いた段落は、内部記事カードとして表示できます。タイトル、説明、画像を現在の記事データから取得するので、リンク先の記事タイトルやアイキャッチを変えた場合も反映されます。外部URLは普通のリンクのままです。
SEOとサイトマップは、記事公開の流れに入れた
SEOは後から別のプラグインを入れる形ではなく、記事編集画面に置きました。記事ごとにSEOタイトル、meta description、canonical URL、index・noindex、OG画像を設定できます。何も入れない項目には、通常タイトル、本文の最初の文章、アイキャッチなどを使います。
公開ページでは、通常のtitleとdescriptionだけでなく、OGP、Xのカード情報、BlogPosting、BreadcrumbListの構造化データを出します。パンくず、カテゴリー、タグ、前後の記事、関連記事も普通のリンクとしてHTMLに入れています。
XMLサイトマップも記事データから動的に作ります。公開済みでindex設定の記事、記事があるカテゴリー、主要ページ、公開しているツールを含めています。記事を公開、更新、非公開化したときや、noindexへ変えたときは、現在の状態がサイトマップに反映されます。記事の更新日はlastmodに使います。URLが50,000件を超えた場合は分割します。
管理画面のダッシュボードには、サイトマップに入っているURL数、最終記事更新日時、現在のXMLを開くリンクがあります。「記事を書いたあと、サイトマップファイルも手で直す」という作業はありません。
RSSフィード、robots.txt、ads.txtもCMS側で出せます。slugを変更したときは古い記事URLから新しいURLへの301転送を自動登録します。必要なら、別の301転送を管理画面から追加・削除することもできます。
アクセス数は記事だけを数えるようにした
ダッシュボードでは、今日、直近7日、直近30日の記事閲覧数を見られます。開始日と終了日を選び、任意の期間でも集計できます。記事一覧では記事ごとの今日、7日、累計を表示します。

トップページやカテゴリーなど、記事以外のページはこの数字に入れません。管理権限でログイン中のアクセス、ボットと判定したアクセス、User-Agentがなくブラウザと判断できないアクセス、GET以外も除外します。同じセッションで同じ記事を30分以内に開き直した分も数えません。
厳密なアクセス解析というより、どの記事が読まれているかを管理画面ですぐ見るための簡易集計です。もっと詳しく見たい場合に備えて、Google Analytics 4の測定IDも設定できます。IDが空ならAnalyticsタグは読み込みません。AdSenseもクライアントIDとスロットを設定でき、空欄なら広告関連のコードを出さないようにしています。
ログイン機能と、複数人で編集するための土台
管理画面はログインしないと開けません。メールアドレスとパスワードで認証します。誰でも登録できる公開登録画面は止めてあります。ログインの試行回数制限も入っています。
管理者ごとにTOTPの二要素認証を有効にできます。認証アプリの6桁コードを使う方式です。パスワードは管理画面から変更でき、記事保存や画像更新などのフォームにはCSRF対策を入れています。
複数の管理者アカウントを作り、それぞれがログインして記事を作成・編集することはできます。記事には最初に作ったユーザーと最後に更新したユーザーのIDを保存します。更新履歴、画像の登録、監査ログにも操作したユーザーを残します。誰がログイン・ログアウトしたか、記事を更新・削除したか、設定を変えたかも監査ログで追えます。
ただ、ここは「共著機能が全部完成している」と書くと少し違います。Googleドキュメントのようなリアルタイム同時編集、編集中表示、コメント、承認フロー、複数著者名の公開表示はまだありません。管理者アカウントの追加も、今は管理画面ではなくサーバーのコマンドから行います。現状は、複数人で同じCMSへ入り、作成者・更新者の記録を残しながら編集できるところまでです。同時に同じ記事を開いた場合の競合防止も、今後必要だと思っています。
サイト全体の設定と、細かい運用機能
管理画面では、サイト名、サイト説明、トップページのdescription、著者表示名、プロフィール文、プロフィール画像、ロゴ、既定OG画像、タイムゾーン、公開連絡先を変更できます。サイト名は改行も使えます。記事に個別のOG画像がない場合は既定画像を使います。
カテゴリーは名前、slug、説明、表示順を管理できます。小さい表示順ほど先に出します。タグも管理でき、記事編集画面で新しいタグ名を入力すると、保存時に作ってその記事へ付けられます。
公開側には、トップ、記事一覧、カテゴリー・タグ一覧、月別アーカイブ、検索、前後の記事、関連記事、about、privacy、contactがあります。PC用のカテゴリー・タグメニューと、スマートフォン用のメニューも同じデータから作っています。
データベースとuploads画像をまとめて保存するバックアップスクリプトもあります。これはCMS画面のボタンではなく、サーバー側で実行する運用用です。バックアップがあるから絶対安全というわけではないので、別の場所へ保管するところまで含めて運用する必要があります。
まだ完成ではないけれど、使いながら直せるのがいい
疲れた・・・・これでもまだちょっとバグがあるんですよね・・・
こうして書き出すと機能は多いのですが、最初から全部を設計できていたわけではありません。画像を連続で消すと絞り込みが戻る、アイキャッチを毎回外で作るのが面倒、表がスマートフォンで崩れる、サイトマップの現在値が分かりにくい、という実際の不便を一つずつ直してきました。
記事データは自分のサーバー側にあり、必要な部分を自分の運用に合わせて変えられます。逆に、サーバーやバックアップも自分で面倒を見る必要があります。外部サービスへ全部任せるより手間はあります。
それでも、文章を書くところから画像、アイキャッチ、目次、公開、SEO、サイトマップ、公開後の確認までが同じ管理画面につながったので、以前より記事を書く途中の細かい作業はかなり減りました。
まだ共著まわりなど途中の部分もあります。使っていてまた面倒なところが出たら、そこを次に直していくつもりです。