1. はじめに: 投稿管理の拡張性とサーバーレス呼び出し最適化の課題
SvelteKit Blog Engine(SKBE)は、日中韓(CJK)を含む多言語対応と多様なメディア形式に対応するセルフホスト型ブログソリューションです。記事の蓄積とコンテンツタイプの多様化に伴い、管理画面の投稿管理(apps/admin/src/routes/posts)において以下の2つの実装課題が生じました:
- 投稿分類体系の多角化: 作成日順の並び替えや単純な言語別切り替えだけでなく、特定カテゴリー別の記事、カテゴリーを持たない独立した固定ページ(Static Pages)、公開前の下書き(Drafts)、そしてカテゴリー未設定の未分類記事を明確に区別し、一箇所で直感的に絞り込める仕組みが必要でした。
- Cloudflare Serverlessインフラ呼び出しの最小化: 管理者がダッシュボード、投稿管理、設定、メディアなどの各メニューを往復するたびに、SvelteKitのSPAルーティングがサーバーの
load関数(__data.json)を呼び出し、Cloudflare Workersの実行回数およびD1読み取りクエリが無駄に累積する構造を改善する必要がありました。
今回のSKBE v1.0.1.2アップデートでは、言語とカテゴリーを直交させた2段階フィルタリング構造を導入し、ブラウザHTTPキャッシュとlocalStorageを活用したスマート無効化パイプラインを構築することで、不要なサーバーレス呼び出しを最小化しました。
2. 2段階直交型マルチフィルタリングアーキテクチャ: 言語第1軸 × 分類第2軸
1) 構造設計: 2段階ツールバーの構成
従来の管理画面は言語タブのみが単一で存在していたため、特定カテゴリーの記事や下書きを探すには複数ページを手動で移動する必要がありました。
v1.0.1.2では、上部に第1段の言語軸と第2段のカテゴリー・特殊分類軸を組み合わせた2次元フィルタリングツールバーを新設しました:
┌────────────────────────────────────────────────────────────────────────────────────────┐
│ [第1段: 言語タブ] │
│ [全体 (35)] [KR 韓国語 (20)] [EN 英語 (10)] [JA 日本語 (5)] │
├────────────────────────────────────────────────────────────────────────────────────────┤
│ [第2段: カテゴリー&特殊分類タブ] │
│ [全体 (35)] │ [📁 開発 (12)] [📁 日常 (6)] │ [📄 固定ページ (4)] [📝 下書き (3)] [📂 未分類 (8)]│
└────────────────────────────────────────────────────────────────────────────────────────┘

2) 特殊分類(Special Classifications)の個別管理
単なるDBカテゴリーのスラッグ一致だけでなく、運用上区別が必要な3つの特殊状態を独立したフィルタータブとして分離しました:
📄 固定ページ(type === 'page'): 紹介、利用規約、プライバシーポリシーなど、通常のブログフィードから独立したページのみを抽出。📝 下書き/一時保存(status === 'draft'): 公開前の未完成記事を集約し、執筆や確認作業を効率化。📂 未分類(type !== 'page' && !category_slug): カテゴリーが割り当てられていない一般記事を抽出し、整理を支援。
3) Svelte 5 Runesによるリアルタイム同期
第1段で言語を切り替えると(例: KR ➔ JA)、日本語の記事で実際に使用されているカテゴリー一覧とその件数、および固定ページや下書きの件数がSvelte 5の$derived.byを通じてメモリ上でリアルタイムに再計算されます:
// [apps/admin/src/routes/posts/+page.svelte]
// 選択中の言語に応じた特殊分類(下書き、固定ページ、未分類)のリアルタイム集計
let specialCounts = $derived.by(() => {
let draft = 0, page = 0, uncategorized = 0;
postsByLang.forEach((p: any) => {
if (p.status === 'draft') draft++;
if (p.type === 'page') page++;
if (p.type !== 'page' && (!p.category_slug || p.category_slug.trim() === '')) {
uncategorized++;
}
});
return { draft, page, uncategorized };
});
4) サイドエフェクト防止: 固有番号の維持とページネーション安全網
- 投稿固有番号(
#1-kr)の保持: 投稿番号のマッピングロジック(postNumberMap)は、フィルター適用後の配列ではなく**全元のデータ(cachedPosts)**を基準に計算されます。これにより、カテゴリー絞り込みを行っても記事固有の識別番号が再割り当てされる不整合を防止しました。 - Out of Bounds防御: 記事が1件しかないカテゴリーを選択した際に前ページのインデックスが残って空白画面が表示されないよう、フィルター切り替え時に即座に
currentPage = 1へ初期化し、$effectで総ページ数の範囲内に自動補正します。
3. Cloudflare Workers呼び出しを最小化するブラウザキャッシュレイヤー
1) サーバーレス環境におけるコストとレイテンシの課題
SvelteKitのSPAナビゲーションは、画面遷移のたびにサーバーのload関数を再実行するため__data.jsonリクエストを送信します。管理者が記事や設定を確認するためにメニューを往復するだけでも、Cloudflare Workersの実行とD1読み取りクエリが累積する構造となっていました。
2) Cache-ControlによるブラウザHTTPキャッシュの適用
これを解決するため、+page.server.tsでブラウザキャッシュヘッダーを設定しました:
// [apps/admin/src/routes/posts/+page.server.ts]
export const load: PageServerLoad = async ({ locals, setHeaders, url }) => {
const isRefreshed = url.searchParams.has('refreshed');
if (!isRefreshed) {
// 通常アクセス時: ブラウザキャッシュを適用しWorkers呼び出しを抑止
setHeaders({
'cache-control': 'private, max-age=86400, stale-while-revalidate=3600'
});
} else {
// 強制更新要求時: サーバーから直接D1最新データを取得
setHeaders({
'cache-control': 'no-store, no-cache, must-revalidate'
});
}
// ... D1クエリ並行実行
};
この設定により、管理者が管理画面内を移動する際、ブラウザのメモリ/ディスクキャッシュから直接レスポンスが返却されるため、Cloudflare Workersの実行およびD1の読み取りリクエストが発生しません。
3) localStorage永続化とスマートなキャッシュ無効化(Cache Busting)
- 初期表示の高速化: ブラウザを閉じて再アクセスした場合でも、
localStorage(skbe_admin_posts_cache_v1)に保存されたメタデータを即座に読み出して画面を描画します。 - データ変更時の自動無効化:
- 記事の新規作成(
new)および編集([id])完了時、/posts?refreshed=${Date.now()}へリダイレクトされ、ブラウザHTTPキャッシュとlocalStorageキャッシュを同時に破棄して最新のD1データを再取得します。 - 記事削除時にもローカルキャッシュを削除し、タイムスタンプ付きの更新を実行します。
- 記事の新規作成(
- 手動更新ボタン(
[🔄]ボタン): 外部でDBが更新された場合などに備え、管理者がワンクリックでキャッシュをクリアして再取得できる更新ボタンをヘッダーに配置しました。
4. 多言語(i18n)辞書の拡張とハブ(Hub)連携の改善
1) 新規UIテキスト14種の言語辞書登録
更新ボタン、2段階フィルタータブ、ツールチップ、状態別の案内メッセージなど、新規追加されたUIテキスト14種を共通辞書(packages/shared/src/i18n/index.ts)に登録しました。
// [packages/shared/src/i18n/index.ts]
"admin.posts.filter_page": { "ko": "정적 페이지", "en": "Static Pages", "ja": "固定ページ" },
"admin.posts.filter_draft": { "ko": "초안", "en": "Drafts", "ja": "下書き" },
"admin.posts.filter_uncategorized": { "ko": "미분류", "en": "Uncategorized", "ja": "未分類" },
"admin.posts.btn_refresh": { "ko": "캐시 비우고 최신 데이터로 새로고침", "en": "Clear cache and refresh with latest data", "ja": "キャッシュをクリアして最新データで更新" },
管理画面の言語設定を英語や日本語に変更した場合でも、フォールバックなしで各言語の適切な文言が表示されます。
2) ハブ(Hub)連携時の多言語サイト名(site_title)自動抽出
記事公開や非公開時にブログハブプラットフォームへ送信されるペイロードにおいて、多言語設定(site_title JSON)から記事の言語(lang)に応じたサイト名を抽出(resolveSiteName)して含めるよう改善しました。
5. おわりに
今回のSKBE v1.0.1.2は、管理画面の探索性を向上させる多次元フィルタリング構造と、サーバーレス環境のネットワークオーバーヘッドやコストを抑えるクライアントキャッシュパイプラインを両立させたリリースです。
データ量が増加してもUIの応答性を維持し、不要なインフラ負荷をかけずに安定して運用できるよう、継続的な改善を行ってまいります。
コメント 0件
コメントを投稿するにはログインが必要です。