--- id: BagelHole/DevOps-Security-Agent-Skills/cloudflare-pages version: "387dd33d" license: MIT install: manual updated: 2026-05-22 --- # cloudflare-pages — Cloudflare Pages lets you host frontend projects and full-stack applications with automatic preview deployments, serverless functions, and zero-config CDN delivery. Deploy from Git repositories or the command line using Wrangler, with built-in support for React, Vue, Next.js, Astro, and other frameworks. Publisher: BagelHole · Stars: 44 · Updated: 2026-05-22 Install (manual): `git clone https://github.com/BagelHole/DevOps-Security-Agent-Skills` ## SKILL.md # Cloudflare Pages Deploy frontend projects with preview builds, edge functions, and global CDN delivery on Cloudflare's network. ## When to Use - Deploying static sites (React, Vue, Astro, Hugo, Next.js static export). - Full-stack applications using Pages Functions for server-side logic. - Projects that need automatic preview deployments per pull request. - Teams that want zero-config CDN with custom domain and TLS. - Migrating from Vercel, Netlify, or GitHub Pages to Cloudflare's ecosystem. ## Prerequisites - Node.js 18+ and npm installed locally. - A Cloudflare account (free tier works for most projects). - Wrangler CLI installed: `npm install -g wrangler`. - Authenticated via `wrangler login` or `CLOUDFLARE_API_TOKEN` environment variable. - Source code in a Git repository (GitHub or GitLab for dashboard integration). ## Project Setup via Wrangler ### Create a New Project ```bash # Create a new Pages project npx wrangler pages project create my-site # List existing projects npx wrangler pages project list # Delete a project (removes all deployments) npx wrangler pages project delete my-site ``` ### Deploy from Local Build Output ```bash # Build your framework first npm run build # Deploy the output directory npx wrangler pages deploy dist --project-name=my-site # Deploy with a custom branch name (triggers preview URL) npx wrangler pages deploy dist --project-name=my-site --branch=feature-auth # Deploy and get the deployment URL in JSON npx wrangler pages deploy dist --project-name=my-site --branch=main 2>&1 | tail -1 ``` ### List and Manage Deployments ```bash # List recent deployments npx wrangler pages deployment list --project-name=my-site # Tail live logs from a deployment npx wrangler pages deployment tail --project-name=my-site --environment=production ``` ## Dashboard Git Integration 1. Navigate to **Workers & Pages > Create application > Pages**. 2. Connect your GitHub or GitLab account. 3. Select the repository and configure: - **Production branch**: `main` - **Build command**: `npm run build` - **Build output directory**: `dist` (or `build`, `.next`, `public` depending on framework) 4. Set environment variables per environment (Production vs Preview). ### Framework Presets Cloudflare auto-detects frameworks. Override if needed: | Framework | Build Command | Output Directory | |------------|----------------------|------------------| | React CRA | `npm run build` | `build` | | Vite | `npm run build` | `dist` | | Next.js | `npx @cloudflare/next-on-pages` | `.vercel/output/static` | | Astro | `npm run build` | `dist` | | Hugo | `hugo` | `public` | | SvelteKit | `npm run build` | `.svelte-kit/cloudflare` | ## Preview Deployments Every non-production branch gets a unique preview URL automatically. ``` # URL format for preview deployments https://..pages.dev https://..pages.dev ``` ### Branch-Based Access Control ```bash # Set preview branch patterns in wrangler.toml (Pages-specific) # Or configure via dashboard: Settings > Builds & deployments # Include branches: feature/*, staging # Exclude branches: dependabot/* ``` ### Preview Comment on Pull Requests Enable the Cloudflare Pages GitHub App to post deployment URLs as PR comments. Configure under **Settings > Builds & deployments > Preview comment**. ## Pages Functions Pages Functions provide server-side logic deployed alongside your static site. Place files in a `functions/` directory at the project root. ### Basic API Route ```typescript // functions/api/hello.ts export const onRequestGet: PagesFunction = async (context) => { return new Response(JSON.stringify({ message: "Hello from the edge" }), { headers: { "Content-Type": "application/json" }, }); }; // functions/api/users/[id].ts — dynamic route parameter export const onRequestGet: PagesFunction = async (context) => { const userId = context.params.id; return new Response(JSON.stringify({ userId }), { headers: { "Content-Type": "application/json" }, }); }; ``` ### Middleware ```typescript // functions/_middleware.ts — runs before all routes export const onRequest: PagesFunction = async (context) => { const authHeader = context.request.headers.get("Authorization"); if (!authHeader || !authHeader.startsWith("Bearer ")) { return new Response("Unauthorized", { status: 401 }); } return context.next(); }; ``` ### Functions with Bindings ```typescript // functions/api/data.ts — using KV and D1 bindings interface Env { MY_KV: KVNamespace; MY_DB: D1Database; MY_BUCKET: R2Bucket; } export const onRequestGet: PagesFunction = async (context) => { // Read from KV const cached = await context.env.MY_KV.get("key"); if (cached) return new Response(cached); // Query D1 const result = await context.env.MY_DB.prepare( "SELECT * FROM items LIMIT 10" ).all(); // Cache in KV await context.env.MY_KV.put("key", JSON.stringify(result.results), { expirationTtl: 300, }); return Response.json(result.results); }; ``` ## Wrangler Configuration ```toml # wrangler.toml — Pages project configuration name = "my-site" compatibility_date = "2024-09-01" pages_build_output_dir = "dist" # KV namespace binding [[kv_namespaces]] binding = "MY_KV" id = "abc123def456" # D1 database binding [[d1_databases]] binding = "MY_DB" database_name = "my-app-db" database_id = "xxxx-yyyy-zzzz" # R2 bucket binding [[r2_buckets]] binding = "MY_BUCKET" bucket_name = "app-assets" # Environment variables [vars] API_BASE_URL = "https://api.example.com" ``` ## Headers and Redirects ### Custom Headers ``` # public/_headers /assets/* Cache-Control: public, max-age=31536000, immutable /* X-Frame-Options: DENY X-Content-Type-Options: nosniff Referrer-Policy: strict-origin-when-cross-origin Permissions-Policy: camera=(), microphone=(), geolocation=() Content-Security-Policy: default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline' /api/* Access-Control-Allow-Origin: https://example.com Access-Control-Allow-Methods: GET, POST, OPTIONS ``` ### Redirects ``` # public/_redirects /old-page /new-page 301 /blog/:slug /posts/:slug 301 /docs/* https://docs.example.com/:splat 302 /home / 302 ``` ## Custom Domains ```bash # Add a custom domain via Cloudflare dashboard: # Pages project > Custom domains > Set up a custom domain # Or via API curl -X POST "https://api.cloudflare.com/client/v4/accounts/$ACCOUNT_ID/pages/projects/my-site/domains" \ -H "Authorization: Bearer $CF_API_TOKEN" \ -H "Content-Type: application/json" \ -d '{"name":"www.example.com"}' ``` ## CI/CD Integration ### GitHub Actions ```yaml # .github/workflows/deploy.yml name: Deploy to Cloudflare Pages on: push: branches: [main] pull_request: jobs: deploy: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version: 20 cache: npm - run: npm ci - run: npm run build - uses: cloudflare/wrangler-action@v3 with: apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }} accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} command: pages deploy dist --project-name=my-site ``` ## Troubleshooting | Symptom | Cause | Fix | |---------|-------|-----| | Build fails with out-of-memory | Build exceeds 1 GB RAM limit | Reduce dependencies; use `NODE_OPTIONS=--max_old_space_size=768` | | Functions return 404 | `functions/` directory not at project root | Move `functions/` to repo root, not inside `src/` | | Preview URL shows old content | Browser cache or stale deployment | Hard refresh; check deployment list for latest commit hash | | Custom domain shows SSL error | DNS not proxied through Cloudflare | Enable orange cloud (proxy) on the CNAME record | | `_headers` file ignored | File not in build output directory | Place in `public/` so it copies to `dist/` during build | | Bindings undefined in Functions | Missing `wrangler.toml` or dashboard config | Add bindings in `wrangler.toml` and redeploy | | 1 MB function size limit exceeded | Too many dependencies bundled | Tree-shake; move large deps to KV or R2 | ## Related Skills - [cloudflare-workers](../cloudflare-workers/) - Edge backend logic and API routes - [cloudflare-r2](../cloudflare-r2/) - Object storage for assets and uploads - [cloudflare-zero-trust](../cloudflare-zero-trust/) - Protect preview deployments with Access policies - [cdn-setup](../../networking/cdn-setup/) - General CDN configuration patterns [View on SkillFed](https://skillfed.io/BagelHole/DevOps-Security-Agent-Skills/cloudflare-pages) · [View on GitHub](https://github.com/BagelHole/DevOps-Security-Agent-Skills)