概要
公開ブログ(/blog)と管理画面(/admin/blog)に対して、複数の機能を同時に実装した。主に検索・絞り込み・ページネーション・日時表示の改善が中心となる。
実装した機能一覧
公開ブログ(/blog)
- キーワード検索: タイトル部分一致でリアルタイムに絞り込む
- カテゴリ絞り込み: ドロップダウンで選択、またはカード上のカテゴリバッジをクリックして絞り込む
- 著者絞り込み: 著者名をクリックするとその著者の記事のみ表示する
- 年月絞り込み: 年・月のドロップダウンで任意の期間に絞り込む
- ページネーション: 1ページ10件表示。10件を超える場合に前後ページのリンクを表示する
- 公開日時の表示: 従来の日付(例: 2026年3月11日)に時刻を追加し、「2026年3月11日 21:00」形式で表示する
管理画面(/admin/blog)
- 著者名の表示: 記事一覧テーブルに著者カラムを追加。
blog_authorsテーブルとの LEFT JOIN で取得する - ブログタイトル・説明文の設定: 管理画面上部にフォームを追加し、
site_settingsテーブルに保存する。公開ブログ側でもこの値を参照して表示する - 下書き一覧ページ:
/admin/blog/drafts/を新設。公開中記事の一覧と下書き記事を分離した - 検索・絞り込み: 公開ブログと同様のフィルタリングをURLパラメータで実装
- ページネーション: 10件ずつ表示し、10件超過時にページ切り替えリンクを表示する
- ボタン折り返しの修正:
flex-wrap: nowrapとwhite-space: nowrapを設定し、操作ボタンが改行されないよう修正した
編集画面(/admin/blog/edit・/admin/blog/new)
- 日時設定:
datetime-local入力を追加し、日付だけでなく時刻(分単位)まで指定可能にした - カテゴリのサジェスト: 既存の全記事に使われているカテゴリを
<datalist>で提示する。入力中にサジェストが表示される - 新規カテゴリの自動作成: サジェストにないカテゴリを入力した場合も、そのまま保存される。カテゴリはカンマ区切りのJSON配列として
blog_posts.categoriesに格納されるため、次回編集時からサジェストに表示される
設計上の判断
フィルタリングはサーバーサイドで処理
URLパラメータ(?q=...&category=...&page=2)を Astro の SSR ページで受け取り、D1 クエリの前後でフィルタリングを適用する構成とした。クライアントサイドJSによる動的フィルタリングは採用しなかった。理由は以下のとおり。
- URLが状態を持つため、フィルタ条件をブックマークや共有リンクとして保存できる
- ページの初期表示にJSが不要で、低スペックデバイスや古いブラウザでも動作する
- Cloudflare Pages の SSR 上で動作するため、サーバー側での処理が自然な設計になる
カテゴリテーブルは作成しない
カテゴリはblog_posts.categoriesにJSON配列として格納されており、別途カテゴリ管理テーブルを用意することも検討した。ただし、今回の要件(既存カテゴリのサジェスト・新規追加)はテーブルなしでも実現できるため、実装の複雑さを抑えるためにテーブル新設は行わなかった。
セキュリティ観点
- SQLはすべてパラメータバインディングを使用しており、SQLインジェクションへの対策を維持している
- ブログタイトル・説明文の保存時には文字列長の上限チェックを追加した(タイトル100文字、説明文300文字)
- カテゴリ入力は文字列として保存するが、テンプレート内では Astro のエスケープが自動適用されるため XSS リスクは低い
- 著者によるフィルタリングは名前の部分一致ではなく完全一致で行い、意図しないクロス著者表示を防いでいる
動作確認
ビルドは npx astro build で成功。Cloudflare Pages のプレビュー環境に対してデプロイ済み。