From claude-resources
Generates GitHub Actions workflows for Cloudflare Pages deployment with production, PR preview, and named preview branch support. Includes wrangler config, retry logic, and security best practices.
How this skill is triggered — by the user, by Claude, or both
Slash command
/claude-resources:dev-cloudflare-pages-ci-setupThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Set up Cloudflare Pages deployment with GitHub Actions workflows for static sites. Supports production deploys, PR preview deploys, and named preview branches.
Set up Cloudflare Pages deployment with GitHub Actions workflows for static sites. Supports production deploys, PR preview deploys, and named preview branches.
Identify the project's build setup:
cat package.json
ls .github/workflows/ 2>/dev/null
cat wrangler.toml 2>/dev/null
Determine: package manager (pnpm/npm/yarn), build command, output directory (dist/, build/, out/), base path (root / or subpath like /pj/project-name/).
--project-name)/ or specific subpath# Cloudflare Pages project configuration
compatibility_date = "2024-12-01"
pnpm add -D wrangler # or npm
For pnpm: add esbuild and workerd to pnpm.onlyBuiltDependencies in package.json.
If the site has a base path (e.g., /pj/project-name/), create public/_redirects:
/ /pj/project-name/ 302
Most static site generators (Astro, Next.js, etc.) copy public/ to output, eliminating CI-time redirect generation.
permissions blocks (least privilege)${{ }} values via env: blocks, never inline in github-script JavaScript (prevents script injection)"${GITHUB_SHA}"npm install -g wrangler@4 (or pnpm exec wrangler when node_modules available)timeout-minutes to all jobs (build: 15, deploy: 20, notify: 5)curl -sSf --max-time 10 for external HTTP callsCloudflare Pages API occasionally returns transient errors (504 Gateway Timeout on /upload-token). Wrap all wrangler pages deploy commands in a bash retry loop:
- name: Deploy to Cloudflare Pages
run: |
for attempt in 1 2 3; do
echo "Deploy attempt $attempt/3..."
if wrangler pages deploy deploy \
--project-name=PROJECT_NAME \
--branch=main \
--commit-hash="${GITHUB_SHA}" \
--commit-message="Production deploy: ${GITHUB_SHA}"; then
echo "Deploy succeeded on attempt $attempt"
exit 0
fi
if [ "$attempt" -lt 3 ]; then
echo "Deploy failed, retrying in 150 seconds..."
sleep 150
fi
done
echo "Deploy failed after 3 attempts"
exit 1
env:
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
timeout-minutes on deploy jobs to 20 (from 10) to accommodate retriesGITHUB_OUTPUT (preview URLs), move the output logic inside the success branch of the if blocknpx wrangler@4 and pnpm exec wrangler variantsTrigger: push to main. Concurrency: production-deploy, cancel-in-progress: false.
permissions:
contents: read
jobs:
build:
# Heavy job — candidate for self-hosted runner via /dev-actions-self-runner
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
# fetch-depth: 0 if project needs git history (e.g., doc history, changelogs)
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v4
with: { node-version: 20, cache: pnpm }
- run: pnpm install --frozen-lockfile
- run: pnpm build
- uses: actions/upload-artifact@v4
with: { name: dist-out, path: dist/, retention-days: 1 }
deploy:
needs: build
runs-on: ubuntu-latest
timeout-minutes: 20
steps:
- uses: actions/download-artifact@v4
with: { name: dist-out, path: deploy/ }
- run: npm install -g wrangler@4
- name: Deploy to Cloudflare Pages (production)
run: |
for attempt in 1 2 3; do
echo "Deploy attempt $attempt/3..."
if wrangler pages deploy deploy \
--project-name=PROJECT_NAME \
--branch=main \
--commit-hash="${GITHUB_SHA}" \
--commit-message="Production deploy: ${GITHUB_SHA}"; then
echo "Deploy succeeded on attempt $attempt"
exit 0
fi
if [ "$attempt" -lt 3 ]; then
echo "Deploy failed, retrying in 150 seconds..."
sleep 150
fi
done
echo "Deploy failed after 3 attempts"
exit 1
env:
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
notify: # Optional IFTTT notification
needs: [build, deploy]
if: always()
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- name: Notify via IFTTT
if: env.IFTTT_PROD_NOTIFY != ''
env:
IFTTT_PROD_NOTIFY: ${{ secrets.IFTTT_PROD_NOTIFY }}
RAW_COMMIT_MSG: ${{ github.event.head_commit.message }}
BUILD_RESULT: ${{ needs.build.result }}
DEPLOY_RESULT: ${{ needs.deploy.result }}
GITHUB_SHA_VAL: ${{ github.sha }}
SERVER_URL: ${{ github.server_url }}
REPO: ${{ github.repository }}
RUN_ID: ${{ github.run_id }}
run: |
if [ "$DEPLOY_RESULT" = "success" ]; then STATUS="succeeded"
elif [ "$BUILD_RESULT" = "failure" ]; then STATUS="failed (build)"
elif [ "$DEPLOY_RESULT" = "failure" ]; then STATUS="failed (deploy)"
else STATUS="cancelled"; fi
COMMIT_MSG=$(echo "$RAW_COMMIT_MSG" | head -1 | sed 's/"/\\"/g')
SHORT_SHA=$(echo "$GITHUB_SHA_VAL" | cut -c1-7)
RUN_URL="${SERVER_URL}/${REPO}/actions/runs/${RUN_ID}"
curl -sSf --max-time 10 -X POST "$IFTTT_PROD_NOTIFY" \
-H 'Content-Type: application/json' \
-d "{
\"value1\": \"Cloudflare Pages deploy ${STATUS}\",
\"value2\": \"${SHORT_SHA} ${COMMIT_MSG}\",
\"value3\": \"${RUN_URL}\"
}" || echo "::warning::IFTTT notification failed"
Trigger: pull_request to main. Concurrency: per-PR, cancel-in-progress: true.
permissions:
contents: read
pull-requests: write
Build job identical to production. Preview job:
deploy/--branch="pr-${PR_NUMBER}"https://pr-${PR_NUMBER}.PROJECT_NAME.pages.devactions/github-script@v8 with marker <!-- cf-preview-pr -->env:: const deployUrl = process.env.DEPLOY_URL;Trigger: push to preview and expreview/**. Concurrency: per-branch, cancel-in-progress: true.
permissions:
contents: read
pull-requests: write
statuses: write
Single-job workflow (build + deploy in one job):
pnpm exec wrangler (node_modules available in same job)createCommitStatus API<!-- cf-preview-branch -->| Secret | Required | Purpose |
|---|---|---|
CLOUDFLARE_API_TOKEN | Yes | Wrangler authentication |
CLOUDFLARE_ACCOUNT_ID | Yes | Cloudflare account identifier |
IFTTT_PROD_NOTIFY | No | IFTTT webhook URL (skipped if not set) |
The Cloudflare Pages project is auto-created on first deploy via wrangler pages deploy.
pnpm build # Verify build works locally
/dev-actions-self-runner — Add self-hosted runner with fallback for build jobs/dev-ci-ifttt-notify — Add IFTTT webhook notificationsnpx claudepluginhub takazudo/claude-resources --plugin claude-resourcesImplements CI/CD for Cloudflare Workers with GitHub Actions and GitLab CI. Automates testing, multi-environment deployments, preview URLs, secrets management, and fixes workflow errors or failures.
Guides Vercel deployments including preview/production deploys, promote, rollback, inspect, --prebuilt builds, and CI/CD workflows for GitHub Actions, GitLab CI, Bitbucket Pipelines.
Deploys static funnel pages to Cloudflare Pages using Wrangler CLI. Covers setup, custom domains, headers, redirects, and performance optimizations like global CDN and Brotli.