mcpField
C
cloudflare-development

cloudflare-development

github.com/mindrallyクラウド・DevOps287695 回インストール3 週間前に更新
リポジトリ

cloudflare-development について

Cloudflareのエッジプラットフォームでアプリを開発する人向けのスキルです。Workers、Pages、関連ストレージの実装に加え、セキュリティ、パフォーマンス、テスト、デプロイの指針を扱います。

主な機能

  1. 01WorkersとPagesの開発
  2. 02KV、D1、R2の利用指針
  3. 03Durable Objectsのパターン
  4. 04エッジセキュリティの実践
  5. 05テストとデプロイの指針

活用シーン

  1. 01Cloudflare Worker APIの実装
  2. 02エッジアプリ用ストレージの選定
  3. 03Cloudflareアプリのテストとデプロイ

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/mindrally/skills --skill cloudflare-development

インストール後、Claude Code で/cloudflare-developmentを入力して読み込まれたか確認します。

詳細

Cloudflare 開発のベストプラクティス

概要

このスキルでは、Workers、Pages、KV ストレージ、D1 データベース、R2 オブジェクトストレージ、Durable Objects など、Cloudflare のエッジプラットフォームでアプリケーションを開発するための包括的なガイドラインを提供します。

基本原則

  • エッジでの実行に最適化した、軽量で高速なコードを記述します
  • コールドスタート時間と実行時間を最小限に抑えます
  • ユースケースに適したストレージソリューションを使用します
  • エッジコンピューティングのセキュリティのベストプラクティスに従います
  • Cloudflare のグローバルネットワークを活用してパフォーマンスを高めます

Cloudflare Workers のガイドライン

基本的な Worker の構造
export default {
  async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> {
    try {
      const url = new URL(request.url);

      // Route handling
      if (url.pathname === '/api/data') {
        return handleApiRequest(request, env);
      }

      return new Response('Not Found', { status: 404 });
    } catch (error) {
      console.error('Worker error:', error);
      return new Response('Internal Server Error', { status: 500 });
    }
  },
} satisfies ExportedHandler<Env>;
環境の種類
interface Env {
  // KV Namespaces
  MY_KV: KVNamespace;

  // D1 Databases
  MY_DB: D1Database;

  // R2 Buckets
  MY_BUCKET: R2Bucket;

  // Durable Objects
  MY_DURABLE_OBJECT: DurableObjectNamespace;

  // Environment Variables
  API_KEY: string;
}
ベストプラクティス
  • 型安全性のために TypeScript を使用します
  • エッジで適切にエラーを処理します
  • 適切なリクエスト検証を実装します
  • バックグラウンドタスクには ctx.waitUntil() を使用します
  • 可能な場合は外部 API の呼び出しを最小限に抑えます

Wrangler の設定

wrangler.toml の構造
name = "my-worker"
main = "src/index.ts"
compatibility_date = "2024-01-01"

[vars]
ENVIRONMENT = "production"

[[kv_namespaces]]
binding = "MY_KV"
id = "abc123"

[[d1_databases]]
binding = "MY_DB"
database_name = "my-database"
database_id = "def456"

[[r2_buckets]]
binding = "MY_BUCKET"
bucket_name = "my-bucket"

[durable_objects]
bindings = [
  { name = "MY_DURABLE_OBJECT", class_name = "MyDurableObject" }
]

[[migrations]]
tag = "v1"
new_classes = ["MyDurableObject"]

KV ストレージのガイドライン

使用パターン
// Writing to KV
await env.MY_KV.put('key', JSON.stringify(data), {
  expirationTtl: 3600, // 1 hour
  metadata: { version: '1.0' },
});

// Reading from KV
const value = await env.MY_KV.get('key', { type: 'json' });

// Listing keys
const list = await env.MY_KV.list({ prefix: 'user:' });
ベストプラクティス
  • 結果整合性を持つ読み取り中心のワークロードには KV を使用します
  • キャッシュデータには適切な TTL を設定します
  • キーに関する追加情報にはメタデータを使用します
  • キャッシュ無効化の戦略を実装します
  • KV の結果整合性モデルを理解しておきます

D1 データベースのガイドライン

クエリパターン
// Parameterized queries (prevent SQL injection)
const results = await env.MY_DB
  .prepare('SELECT * FROM users WHERE id = ?')
  .bind(userId)
  .all();

// Batch operations
const batch = await env.MY_DB.batch([
  env.MY_DB.prepare('INSERT INTO logs (message) VALUES (?)').bind('log1'),
  env.MY_DB.prepare('INSERT INTO logs (message) VALUES (?)').bind('log2'),
]);

// First result only
const user = await env.MY_DB
  .prepare('SELECT * FROM users WHERE email = ?')
  .bind(email)
  .first();
スキーマ管理
-- migrations/0001_initial.sql
CREATE TABLE users (
  id INTEGER PRIMARY KEY AUTOINCREMENT,
  email TEXT UNIQUE NOT NULL,
  name TEXT NOT NULL,
  created_at DATETIME DEFAULT CURRENT_TIMESTAMP
);

CREATE INDEX idx_users_email ON users(email);
ベストプラクティス
  • 必ずパラメーター化クエリを使用します
  • 適切なインデックスを作成します
  • 複数の書き込みにはバッチ操作を使用します
  • クエリをシンプルかつ効率的に保ちます
  • スキーマの変更にはマイグレーションを使用します

R2 オブジェクトストレージのガイドライン

使用パターン
// Upload object
await env.MY_BUCKET.put('uploads/file.pdf', fileData, {
  httpMetadata: {
    contentType: 'application/pdf',
  },
  customMetadata: {
    uploadedBy: userId,
  },
});

// Download object
const object = await env.MY_BUCKET.get('uploads/file.pdf');
if (object) {
  return new Response(object.body, {
    headers: {
      'Content-Type': object.httpMetadata?.contentType || 'application/octet-stream',
    },
  });
}

// List objects
const list = await env.MY_BUCKET.list({ prefix: 'uploads/' });

// Delete object
await env.MY_BUCKET.delete('uploads/file.pdf');
ベストプラクティス
  • 適切なコンテンツタイプを設定します
  • 大きなファイルにはマルチパートアップロードを使用します
  • 適切なアクセス制御を実装します
  • クライアントから直接アップロードする場合は、署名付き URL を使用します
  • 論理的なプレフィックスを使用してオブジェクトを整理します

Durable Objects のガイドライン

実装
export class ChatRoom implements DurableObject {
  private state: DurableObjectState;
  private sessions: Map<WebSocket, { id: string }>;

  constructor(state: DurableObjectState, env: Env) {
    this.state = state;
    this.sessions = new Map();
  }

  async fetch(request: Request): Promise<Response> {
    const url = new URL(request.url);

    if (url.pathname === '/websocket') {
      const pair = new WebSocketPair();
      await this.handleSession(pair[1]);
      return new Response(null, { status: 101, webSocket: pair[0] });
    }

    return new Response('Not Found', { status: 404 });
  }

  async handleSession(webSocket: WebSocket) {
    webSocket.accept();

    webSocket.addEventListener('message', async (event) => {
      // Handle messages
    });

    webSocket.addEventListener('close', () => {
      this.sessions.delete(webSocket);
    });
  }
}
ベストプラクティス
  • 調整やステートフルなロジックに使用します
  • 適切な WebSocket 処理を実装します
  • 永続化にはストレージ API を使用します
  • コスト最適化のためにハイバネーションを処理します
  • 単一の調整ポイントを設けるパターンを前提に設計します

Cloudflare Pages のガイドライン

プロジェクト構造
my-pages-project/
├── public/           # Static assets
├── functions/        # Pages Functions
│   ├── api/
│   │   └── [endpoint].ts
│   └── _middleware.ts
├── src/              # Application source
└── wrangler.toml     # Configuration
Pages Functions
// functions/api/users.ts
export const onRequestGet: PagesFunction<Env> = async (context) => {
  const users = await context.env.MY_DB
    .prepare('SELECT * FROM users')
    .all();

  return Response.json(users.results);
};

export const onRequestPost: PagesFunction<Env> = async (context) => {
  const body = await context.request.json();
  // Handle POST
  return Response.json({ success: true });
};

エッジセキュリティのベストプラクティス

リクエスト検証
function validateRequest(request: Request): boolean {
  // Check content type
  const contentType = request.headers.get('Content-Type');
  if (request.method === 'POST' && !contentType?.includes('application/json')) {
    return false;
  }

  // Check origin (CORS)
  const origin = request.headers.get('Origin');
  if (origin && !ALLOWED_ORIGINS.includes(origin)) {
    return false;
  }

  return true;
}
認証
async function verifyAuth(request: Request, env: Env): Promise<boolean> {
  const authHeader = request.headers.get('Authorization');
  if (!authHeader?.startsWith('Bearer ')) {
    return false;
  }

  const token = authHeader.slice(7);
  // Verify JWT or API key
  return await verifyToken(token, env);
}
レート制限
async function checkRateLimit(ip: string, env: Env): Promise<boolean> {
  const key = `ratelimit:${ip}`;
  const current = await env.MY_KV.get(key, { type: 'json' }) as number || 0;

  if (current >= 100) { // 100 requests per window
    return false;
  }

  await env.MY_KV.put(key, JSON.stringify(current + 1), {
    expirationTtl: 60, // 1 minute window
  });

  return true;
}

パフォーマンスの最適化

キャッシュ戦略
// Cache API usage
const cache = caches.default;

async function handleRequest(request: Request): Promise<Response> {
  // Check cache
  const cached = await cache.match(request);
  if (cached) {
    return cached;
  }

  // Generate response
  const response = await generateResponse(request);

  // Cache response
  const cacheResponse = new Response(response.body, response);
  cacheResponse.headers.set('Cache-Control', 'public, max-age=3600');
  ctx.waitUntil(cache.put(request, cacheResponse.clone()));

  return cacheResponse;
}
バックグラウンド処理
export default {
  async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> {
    // Respond immediately
    const response = Response.json({ status: 'accepted' });

    // Process in background
    ctx.waitUntil(processInBackground(request, env));

    return response;
  },
};

テスト

ローカル開発
# Start local development server
wrangler dev

# Run with local persistence
wrangler dev --persist

# Test with specific environment
wrangler dev --env staging
ユニットテスト
import { unstable_dev } from 'wrangler';

describe('Worker', () => {
  let worker: UnstableDevWorker;

  beforeAll(async () => {
    worker = await unstable_dev('src/index.ts', {
      experimental: { disableExperimentalWarning: true },
    });
  });

  afterAll(async () => {
    await worker.stop();
  });

  test('returns 200 for valid request', async () => {
    const response = await worker.fetch('/api/health');
    expect(response.status).toBe(200);
  });
});

デプロイ

本番環境へのデプロイ
# Deploy to production
wrangler deploy

# Deploy to specific environment
wrangler deploy --env production

# Deploy with secrets
wrangler secret put API_KEY
CI/CD 統合
# .github/workflows/deploy.yml
name: Deploy Worker

on:
  push:
    branches: [main]

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: cloudflare/wrangler-action@v3
        with:
          apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}

避けるべきよくある落とし穴

  1. エッジで適切にエラーを処理しない
  2. 外部 API を呼び出しすぎる
  3. Worker の CPU とメモリの制限を無視する
  4. ユースケースに適したストレージを使用しない
  5. KV の結果整合性を考慮しない
  6. 適切なレート制限を実装しない
  7. シークレットをコードにハードコードする
  8. コールドスタートの最適化を無視する

コメント