Cloudflare Pages

WebホスティングJAMstackエッジコンピューティングWorkersCI/CD無制限帯域幅

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"]
}