Cloudflare Pages
Webホスティングプラットフォーム
Cloudflare Pages
概要
Cloudflare PagesはCloudflareのグローバルネットワークを活用したJAMstack向けプラットフォームです。超高速配信、無制限の無料帯域幅、Workers統合による強力なエッジコンピューティング機能を提供し、無料で無制限の帯域幅により急速に採用拡大しています。Cloudflare Workersとの統合により、エッジでの高度な処理が可能で、コスト効率の高さで注目を集めています。
詳細
2021年にローンチされたCloudflare Pagesは、Cloudflareの強力なグローバルインフラストラクチャーを背景とした静的サイトホスティングサービスです。世界中に分散された330以上のデータセンターを活用し、ユーザーに最も近い場所からコンテンツを配信。無制限の帯域幅、高速ビルド、Git統合、Cloudflare Workersとのシームレスな連携により、従来の静的サイトホスティングを超えた動的機能も実現可能。特にパフォーマンスとコスト効率を重視する開発者に人気で、大規模なトラフィックでも安定したパフォーマンスを維持します。
メリット・デメリット
メリット
- 無制限の無料帯域幅: トラフィック量に関係なく追加料金なし
- グローバル高速配信: 330+のデータセンターからの超高速配信
- Workers統合: エッジでの高度なサーバーサイド処理
- 優秀なセキュリティ: DDoS攻撃対策とWeb Application Firewall
- 高いコストパフォーマンス: 企業レベルの機能を低コストで提供
- Git統合: GitHub、GitLabとの自動連携
- 高速ビルド: 効率的なビルドプロセスと並列処理
デメリット
- 新しいプラットフォーム: 他社と比べて機能や情報が限定的
- 学習コストの高さ: Workers統合の理解に時間が必要
- プラグインエコシステム: NetlifyやVercelと比べてサードパーティ統合が少ない
- サポート: 無料プランでは限定的なサポート
参考ページ
書き方の例
基本的なセットアップとプロジェクト設定
# Wrangler CLIのインストール
npm install -g wrangler@latest
# ログインと認証
wrangler login
# 新しいPagesプロジェクトの作成
wrangler pages project create my-project
# ローカル開発環境
wrangler pages dev ./dist
// wrangler.jsonc - Pages設定
{
"name": "my-pages-project",
"compatibility_date": "2025-01-15",
"pages_build_output_dir": "./dist",
"functions": "./functions",
"kv_namespaces": [
{
"binding": "MY_KV",
"id": "your-kv-namespace-id",
"preview_id": "your-preview-kv-namespace-id"
}
],
"vars": {
"API_BASE_URL": "https://api.example.com"
}
}
静的サイトデプロイ
# 直接デプロイ
wrangler pages deploy ./dist --project-name=my-project
# Git統合デプロイ(推奨)
# GitHubリポジトリとの連携は管理画面で設定
# プレビューデプロイ
wrangler pages deploy ./dist --project-name=my-project --branch=feature/new-feature
# GitHub Actions でのデプロイ
name: Deploy to Cloudflare Pages
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
permissions:
contents: read
deployments: write
steps:
- uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '18'
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Build
run: npm run build
- name: Deploy to Cloudflare Pages
uses: cloudflare/wrangler-action@v3
with:
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
command: pages deploy dist --project-name=my-project
フレームワーク統合(Next.js、React、Vue)
// next.config.js - Cloudflare Pages対応
/** @type {import('next').NextConfig} */
const nextConfig = {
output: 'export',
trailingSlash: true,
images: {
unoptimized: true,
},
// 静的エクスポート用設定
experimental: {
outputStandalone: true,
},
};
module.exports = nextConfig;
# Nuxt.jsプロジェクトの設定
npx nuxi@latest init my-nuxt-app
cd my-nuxt-app
# nuxt.config.ts
export default defineNuxtConfig({
nitro: {
preset: 'cloudflare-pages',
},
experimental: {
payloadExtraction: false,
},
});
# ビルドとデプロイ
npm run build
wrangler pages deploy dist --project-name=my-nuxt-app
// vite.config.ts - Vite + Cloudflare Pages
import { defineConfig } from 'vite';
import { resolve } from 'path';
export default defineConfig({
build: {
outDir: 'dist',
rollupOptions: {
input: {
main: resolve(__dirname, 'index.html'),
},
},
},
define: {
__CLOUDFLARE_PAGES__: JSON.stringify(true),
},
});
カスタムドメインとSSL
# カスタムドメインの追加
wrangler pages domain add my-project example.com
# ドメイン一覧の確認
wrangler pages domain list
# DNS設定(CNAMEレコード)
# example.com -> your-project.pages.dev
// _headers ファイル - カスタムヘッダー設定
/*
X-Frame-Options: DENY
X-Content-Type-Options: nosniff
Referrer-Policy: strict-origin-when-cross-origin
Permissions-Policy: camera=(), microphone=(), geolocation=()
/api/*
Access-Control-Allow-Origin: https://example.com
Access-Control-Allow-Methods: GET, POST, PUT, DELETE
Access-Control-Allow-Headers: Content-Type, Authorization
/static/*
Cache-Control: public, max-age=31536000, immutable
// _redirects ファイル - リダイレクト設定
# 301リダイレクト
/old-page /new-page 301
# SPAフォールバック
/* /index.html 200
# API プロキシ
/api/* https://backend.example.com/api/:splat 200
# 条件付きリダイレクト
/blog/* /new-blog/:splat 301 Country=US
サーバーレスファンクションとAPI
// functions/api/hello.ts - Pages Functions
interface Env {
MY_KV: KVNamespace;
DB: D1Database;
}
export const onRequest: PagesFunction<Env> = async (context) => {
const { request, env, params } = context;
const url = new URL(request.url);
const name = url.searchParams.get('name') || 'World';
// KVストレージへの保存
await env.MY_KV.put(`greeting:${Date.now()}`, JSON.stringify({
name,
timestamp: new Date().toISOString(),
}));
return new Response(JSON.stringify({
message: `Hello ${name}!`,
timestamp: new Date().toISOString(),
source: 'Cloudflare Pages Functions'
}), {
headers: {
'Content-Type': 'application/json',
'Cache-Control': 'public, max-age=60',
},
});
};
// functions/api/users/[id].ts - 動的ルーティング
interface Env {
DB: D1Database;
}
export const onRequestGet: PagesFunction<Env> = async (context) => {
const { params, env } = context;
const userId = params.id as string;
try {
const stmt = env.DB.prepare('SELECT * FROM users WHERE id = ?');
const result = await stmt.bind(userId).first();
if (!result) {
return new Response('User not found', { status: 404 });
}
return Response.json(result);
} catch (error) {
return new Response('Database error', { status: 500 });
}
};
export const onRequestPost: PagesFunction<Env> = async (context) => {
const { request, env } = context;
const userData = await request.json();
try {
const stmt = env.DB.prepare('INSERT INTO users (name, email) VALUES (?, ?)');
const result = await stmt.bind(userData.name, userData.email).run();
return Response.json({
id: result.meta.last_row_id,
...userData
}, { status: 201 });
} catch (error) {
return new Response('Database error', { status: 500 });
}
};
CI/CDと本番最適化
// functions/_middleware.ts - ミドルウェア
interface Env {
RATE_LIMITER: KVNamespace;
}
export const onRequest: PagesFunction<Env> = async (context) => {
const { request, env, next } = context;
const clientIP = request.headers.get('CF-Connecting-IP') || 'unknown';
// レート制限
const key = `rate_limit:${clientIP}`;
const current = await env.RATE_LIMITER.get(key);
const count = parseInt(current || '0');
if (count > 100) {
return new Response('Rate limit exceeded', { status: 429 });
}
await env.RATE_LIMITER.put(key, (count + 1).toString(), {
expirationTtl: 3600, // 1時間
});
// セキュリティヘッダーの追加
const response = await next();
const newResponse = new Response(response.body, response);
newResponse.headers.set('X-Frame-Options', 'DENY');
newResponse.headers.set('X-Content-Type-Options', 'nosniff');
newResponse.headers.set('Strict-Transport-Security', 'max-age=31536000');
return newResponse;
};
# Wrangler コマンド集
wrangler pages project list # プロジェクト一覧
wrangler pages deployment list # デプロイ履歴
wrangler pages download # サイトのダウンロード
wrangler pages deployment tail # リアルタイムログ
# KV操作
wrangler kv:namespace create "MY_KV" # KV namespace作成
wrangler kv:key put "key" "value" # データ保存
wrangler kv:key get "key" # データ取得
# D1データベース操作
wrangler d1 create my-database # D1データベース作成
wrangler d1 execute my-database --file=schema.sql # SQLファイル実行
# Analytics確認
wrangler pages deployment view --project-name=my-project
// wrangler.jsonc - 高度な設定
{
"name": "my-advanced-project",
"compatibility_date": "2025-01-15",
"pages_build_output_dir": "./dist",
"functions": "./functions",
// KV Namespaces
"kv_namespaces": [
{
"binding": "CACHE",
"id": "cache-namespace-id",
"preview_id": "cache-preview-namespace-id"
}
],
// D1 Databases
"d1_databases": [
{
"binding": "DB",
"database_name": "my-database",
"database_id": "database-id"
}
],
// Environment Variables
"vars": {
"ENVIRONMENT": "production",
"API_VERSION": "v1"
},
// Secrets (use wrangler secret put)
// "secrets": ["DATABASE_URL", "JWT_SECRET"]
}