cloudflare-development について
Cloudflareのエッジプラットフォームでアプリを開発する人向けのスキルです。Workers、Pages、関連ストレージの実装に加え、セキュリティ、パフォーマンス、テスト、デプロイの指針を扱います。
主な機能
- 01WorkersとPagesの開発
- 02KV、D1、R2の利用指針
- 03Durable Objectsのパターン
- 04エッジセキュリティの実践
- 05テストとデプロイの指針
活用シーン
- 01Cloudflare Worker APIの実装
- 02エッジアプリ用ストレージの選定
- 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 }}
避けるべきよくある落とし穴
- エッジで適切にエラーを処理しない
- 外部 API を呼び出しすぎる
- Worker の CPU とメモリの制限を無視する
- ユースケースに適したストレージを使用しない
- KV の結果整合性を考慮しない
- 適切なレート制限を実装しない
- シークレットをコードにハードコードする
- コールドスタートの最適化を無視する
コメント
Markdown 対応