Cloudflare Pages
Web Hosting Platform
Cloudflare Pages
Overview
Cloudflare Pages is a JAMstack platform leveraging Cloudflare's global network. Offering ultra-fast delivery, unlimited free bandwidth, and powerful edge computing with Workers integration, it's rapidly expanding adoption due to unlimited free bandwidth. Advanced edge processing capabilities through Cloudflare Workers integration make it notable for cost efficiency.
Details
Launched in 2021, Cloudflare Pages is a static site hosting service backed by Cloudflare's powerful global infrastructure. Utilizing over 330 data centers distributed worldwide, it delivers content from the closest location to users. With unlimited bandwidth, fast builds, Git integration, and seamless integration with Cloudflare Workers, it enables dynamic functionality beyond traditional static site hosting. Particularly popular among developers who prioritize performance and cost efficiency, maintaining stable performance even with large-scale traffic.
Advantages and Disadvantages
Advantages
- Unlimited Free Bandwidth: No additional charges regardless of traffic volume
- Global High-Speed Delivery: Ultra-fast delivery from 330+ data centers
- Workers Integration: Advanced server-side processing at the edge
- Excellent Security: DDoS attack protection and Web Application Firewall
- High Cost Performance: Enterprise-level features at low cost
- Git Integration: Automatic integration with GitHub and GitLab
- Fast Builds: Efficient build process and parallel processing
Disadvantages
- New Platform: Limited features and information compared to competitors
- High Learning Curve: Time required to understand Workers integration
- Plugin Ecosystem: Fewer third-party integrations compared to Netlify or Vercel
- Support: Limited support on free plan
Reference Pages
- Cloudflare Pages Official Site
- Cloudflare Pages Documentation
- Cloudflare Workers
- Cloudflare Developer Documentation
Code Examples
Basic Setup and Project Configuration
# Install Wrangler CLI
npm install -g wrangler@latest
# Login and authentication
wrangler login
# Create new Pages project
wrangler pages project create my-project
# Local development environment
wrangler pages dev ./dist
// wrangler.jsonc - Pages configuration
{
"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"
}
}
Static Site Deployment
# Direct deployment
wrangler pages deploy ./dist --project-name=my-project
# Git integration deployment (recommended)
# GitHub repository integration configured in dashboard
# Preview deployment
wrangler pages deploy ./dist --project-name=my-project --branch=feature/new-feature
# Deploy with 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
Framework Integration (Next.js, React, Vue)
// next.config.js - Cloudflare Pages support
/** @type {import('next').NextConfig} */
const nextConfig = {
output: 'export',
trailingSlash: true,
images: {
unoptimized: true,
},
// Static export configuration
experimental: {
outputStandalone: true,
},
};
module.exports = nextConfig;
# Nuxt.js project configuration
npx nuxi@latest init my-nuxt-app
cd my-nuxt-app
# nuxt.config.ts
export default defineNuxtConfig({
nitro: {
preset: 'cloudflare-pages',
},
experimental: {
payloadExtraction: false,
},
});
# Build and deploy
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),
},
});
Custom Domains and SSL
# Add custom domain
wrangler pages domain add my-project example.com
# List domains
wrangler pages domain list
# DNS configuration (CNAME record)
# example.com -> your-project.pages.dev
// _headers file - Custom header configuration
/*
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 file - Redirect configuration
# 301 redirect
/old-page /new-page 301
# SPA fallback
/* /index.html 200
# API proxy
/api/* https://backend.example.com/api/:splat 200
# Conditional redirect
/blog/* /new-blog/:splat 301 Country=US
Serverless Functions and APIs
// 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';
// Save to KV storage
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 - Dynamic routing
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 and Production Optimization
// functions/_middleware.ts - Middleware
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';
// Rate limiting
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 hour
});
// Add security headers
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 command collection
wrangler pages project list # List projects
wrangler pages deployment list # Deployment history
wrangler pages download # Download site
wrangler pages deployment tail # Real-time logs
# KV operations
wrangler kv:namespace create "MY_KV" # Create KV namespace
wrangler kv:key put "key" "value" # Save data
wrangler kv:key get "key" # Get data
# D1 database operations
wrangler d1 create my-database # Create D1 database
wrangler d1 execute my-database --file=schema.sql # Execute SQL file
# Check analytics
wrangler pages deployment view --project-name=my-project
// wrangler.jsonc - Advanced configuration
{
"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"]
}