Cloudflare Pages + KVで実現する爆速CMS:実装記録と課題

作成日:
約12分で読めます

どーも、ちょりんだです。

このブログのCMS機能開発が順調に進んでいます。Cloudflare Pages + KVという組み合わせで、従来のCMSとは異なるアプローチを試みています。

今回は、現在の開発状況、技術的な課題、そして今後の改善計画について詳しく解説します。

はじめに:なぜCloudflare Pages + KVを選んだか

従来のCMSはMySQLやPostgreSQLなどのリレーショナルデータベースを使用しますが、Cloudflare KVには以下の利点があります:

  1. 爆速なパフォーマンス: エッジロケーションにデータが分散
  2. スケーラビリティ: 自動的なスケーリング、運用不要
  3. コスト効率: 従量課金制、小規模サイトならほぼ無料
  4. グローバル配信: 世界中のユーザーに高速アクセス

ただし、KVには制限もあります。その制限をどう乗り越えるかが開発の鍵でした。

現在のアーキテクチャ

全体像

┌─────────────────┐    ┌─────────────────┐    ┌─────────────────┐
│   Admin Panel   │    │   Cloudflare    │    │   Astro Site    │
│   (Astro)       │───▶│   Functions     │───▶│   (Static)      │
│                 │    │   (API)         │    │                 │
└─────────────────┘    └─────────────────┘    └─────────────────┘


                       ┌─────────────────┐
                       │  Cloudflare KV  │
                       │  (Key-Value)    │
                       └─────────────────┘

データフロー

  1. 書き込み: Admin Panel → Functions → KV
  2. 読み込み: KV → Functions → Astro Site
  3. キャッシュ: エッジロケーションでの自動キャッシュ

実装の詳細

1. KVストレージの設計

KVは単純なKey-Valueストアなので、CMSのデータ構造を工夫する必要がありました。

// 記事データの構造
const postStructure = {
  id: "post-123456",
  slug: "my-blog-post",
  data: {
    title: "記事タイトル",
    description: "記事説明",
    pubDate: "2026-03-21",
    updatedDate: "2026-03-21",
    tags: ["技術", "Astro"],
    status: "published"
  },
  body: "記事本文...",
  metadata: {
    author: "admin",
    version: 1,
    lastModified: "2026-03-21T10:00:00Z"
  }
};

Key設計の戦略:

  • 記事: post:{id}
  • 設定: config:{key}
  • インデックス: index:{type}:{value}

2. APIエンドポイントの実装

Cloudflare FunctionsでRESTful APIを構築:

// src/pages/api/posts.js
export async function GET({ request }) {
  try {
    const postsStore = await getPostStore({ locals: globalThis });
    const allKeys = await postsStore.list();
    const allPosts = [];
    
    for (const key of allKeys.keys) {
      if (key.name.startsWith('post:')) {
        const postJson = await postsStore.get(key.name);
        if (postJson) {
          const post = JSON.parse(postJson);
          if (post.data.status === 'published') {
            allPosts.push(post);
          }
        }
      }
    }
    
    return new Response(JSON.stringify({
      success: true,
      data: allPosts
    }), {
      headers: { 'Content-Type': 'application/json' }
    });
  } catch (error) {
    return new Response(JSON.stringify({
      success: false,
      error: error.message
    }), {
      status: 500,
      headers: { 'Content-Type': 'application/json' }
    });
  }
}

3. 管理画面の構築

Astroで管理画面を実装:

---
// src/pages/admin/index.astro
import BaseLayout from '../../layouts/BaseLayout.astro';

// 認証チェック
const authToken = Astro.cookies.get('auth_token')?.value;
if (!authToken) {
  return Astro.redirect('/admin/login');
}

// 記事データの取得
const response = await fetch(`${import.meta.env.SITE}/api/posts`);
const result = await response.json();
const posts = result.success ? result.data : [];
---

<BaseLayout title="CMS管理画面">
  <!-- 管理画面UI -->
</BaseLayout>

技術的な課題と解決策

1. KVの制限への対応

課題: KVには1つの値あたり25MB、1つのNamespaceあたり1GBの制限

解決策:

  • 大きな記事は分割して保存
  • 画像やメディアは別ストレージ(R2)に保存
  • 古い記事はアーカイブ機能で移動
// 大きな記事の分割保存
async function saveLargePost(post) {
  const chunkSize = 1000000; // 1MB
  const chunks = [];
  
  for (let i = 0; i < post.body.length; i += chunkSize) {
    const chunk = post.body.slice(i, i + chunkSize);
    chunks.push(chunk);
    
    await postsStore.put(`post:${post.id}:chunk:${i}`, chunk);
  }
  
  // メタデータのみメインキーに保存
  await postsStore.put(`post:${post.id}`, {
    ...post,
    body: null,
    chunks: chunks.length
  });
}

2. 検索機能の実装

課題: KVはクエリ機能がないため、全文検索が難しい

解決策:

  • インデックス用のKeyを別途作成
  • タイトルとタグで部分検索を実装
  • Pagefindのような静的検索ツールを併用
// 検索インデックスの作成
async function createSearchIndex(post) {
  const keywords = [
    ...post.data.title.split(' '),
    ...post.data.tags,
    // 本文からキーワード抽出(簡易版)
    ...post.body.split(' ').filter(word => word.length > 3)
  ];
  
  for (const keyword of keywords) {
    const indexKey = `search:${keyword.toLowerCase()}`;
    const existing = await postsStore.get(indexKey);
    const posts = existing ? JSON.parse(existing) : [];
    
    if (!posts.includes(post.id)) {
      posts.push(post.id);
      await postsStore.put(indexKey, JSON.stringify(posts));
    }
  }
}

3. リアルタイム更新の課題

課題: KVの propagation delay(最大60秒)

解決策:

  • 即時更新が必要なデータはDurable Objectsを使用
  • UI側で楽観的更新を実装
  • WebSocketでリアルタイム通知
// 楽観的更新の実装
async function updatePost(postId, updates) {
  // まずUIを更新
  updateUIOptimistically(updates);
  
  try {
    // KVに保存
    await postsStore.put(`post:${postId}`, updates);
    
    // 成功したら確定
    confirmUpdate();
  } catch (error) {
    // 失敗したらロールバック
    rollbackUpdate();
    showError(error);
  }
}

4. バージョン管理の実装

課題: KVは単純な上書きしかできない

解決策:

  • バージョン番号で別Keyに保存
  • 差分データで容量を節約
  • 定期的な古いバージョンの削除
// バージョン管理の実装
async function savePostVersion(post, version) {
  const versionKey = `post:${post.id}:version:${version}`;
  await postsStore.put(versionKey, {
    ...post,
    metadata: {
      ...post.metadata,
      version,
      savedAt: new Date().toISOString()
    }
  });
  
  // 最新バージョンへの参照
  await postsStore.put(`post:${post.id}:latest`, version);
}

パフォーマンスの最適化

1. キャッシュ戦略

// マルチレベルキャッシュ
const cacheStrategy = {
  // エッジキャッシュ(KV)
  edge: {
    ttl: 3600, // 1時間
    staleWhileRevalidate: 86400 // 1日
  },
  
  // ブラウザキャッシュ
  browser: {
    html: 300, // 5分
    api: 60, // 1分
    assets: 86400 // 1日
  }
};

2. データの圧縮

// JSONデータの圧縮
import { gzip, ungzip } from 'pako';

async function compressData(data) {
  const jsonString = JSON.stringify(data);
  const compressed = gzip(jsonString);
  return compressed;
}

async function decompressData(compressed) {
  const decompressed = ungzip(compressed);
  return JSON.parse(decompressed);
}

3. バッチ処理

// 複数のKV操作をバッチ化
async function batchOperations(operations) {
  const promises = operations.map(op => {
    switch (op.type) {
      case 'put':
        return postsStore.put(op.key, op.value);
      case 'get':
        return postsStore.get(op.key);
      case 'delete':
        return postsStore.delete(op.key);
    }
  });
  
  return Promise.all(promises);
}

開発体験の改善

1. ローカル開発環境

// ローカルKVシミュレーター
class LocalKVSimulator {
  constructor() {
    this.store = new Map();
  }
  
  async get(key) {
    return this.store.get(key) || null;
  }
  
  async put(key, value) {
    this.store.set(key, value);
  }
  
  async list() {
    const keys = Array.from(this.store.keys())
      .filter(key => key.startsWith('post:'))
      .map(name => ({ name }));
    
    return { keys };
  }
}

2. デバッグツール

// KV操作のログ
function createKVLogger(kv) {
  return new Proxy(kv, {
    get(target, prop) {
      if (typeof target[prop] === 'function') {
        return function(...args) {
          console.log(`KV.${prop}:`, args);
          return target[prop].apply(target, args);
        };
      }
      return target[prop];
    }
  });
}

3. テスト環境

// KVのテストユーティリティ
class KVTestHelper {
  constructor() {
    this.testData = new Map();
  }
  
  async setupTestData(posts) {
    for (const post of posts) {
      this.testData.set(`post:${post.id}`, JSON.stringify(post));
    }
  }
  
  async cleanup() {
    this.testData.clear();
  }
}

今後の改善計画

短期的改善(1ヶ月)

  1. 検索機能の強化

    • 全文検索エンジンの導入
    • ファセット検索の実装
    • 検索結果のランキング
  2. UI/UXの改善

    • リッチテキストエディタの導入
    • ドラッグ&ドロップでの画像アップロード
    • プレビュー機能のリアルタイム化
  3. パフォーマンス最適化

    • データの事前読み込み
    • インクリメンタル更新
    • オフライン対応

中期的改善(3ヶ月)

  1. 機能拡張

    • マルチユーザー対応
    • 権限管理システム
    • コメント機能
  2. データ管理

    • 自動バックアップ機能
    • データエクスポート/インポート
    • バージョン管理の強化
  3. インテグレーション

    • 外部API連携
    • Webhook対応
    • サードパーティーツール連携

長期的改善(6ヶ月)

  1. スケーラビリティ

    • マルチリージョン対応
    • 負荷分散
    • パフォーマンス監視
  2. 高度な機能

    • A/Bテスト機能
    • パーソナライズ機能
    • 分析ダッシュボード

まとめ

Cloudflare Pages + KVでのCMS開発は、従来のアプローチとは異なる課題がありますが、適切な設計と実装で十分に実用可能です。

現在の成果:

  • ✅ 基本的なCRUD機能の実装
  • ✅ 管理画面の構築
  • ✅ 認証システムの導入
  • ✅ パフォーマンスの最適化
  • ✅ 開発環境の整備

残された課題:

  • 🔄 検索機能の強化
  • 🔄 リアルタイム更新の改善
  • 🔄 大規模データへの対応
  • 🔄 高度なCMS機能

このCMSは、小規模〜中規模のブログやサイトに最適なソリューションになる可能性を秘めています。KVの制限を工夫で乗り越えながら、より良いCMSを目指して開発を続けていきます。


関連記事: CMS開発進捗:Cloudflare KVでのデータ永続化に挑戦中

タグ: #CMS #Cloudflare #Astro #KV

Share