Catatan dari produksi
CI/CDGitHub ActionsCloudflare

CI/CD: GitHub Actions → Cloudflare Pages

Kode Anda tinggal di repo GitHub privat (harryosmar/pangaea.id), dan setiap perubahan mengalir lewat Pull Request. Inilah bagian yang mengubah "PR ter-merge" jadi "situs live ter-update" — rangkaian CI/CD-nya, ujung ke ujung, lengkap dengan tiap perintah yang benar-benar Anda jalankan.

Satu fitur = satu Pull Request

Tak ada yang dirilis langsung ke main. Perubahan dimulai di branch, membuka PR, dan baru ter-merge setelah cek-nya hijau. Merge hijau itulah yang menerbitkan situs.

feature branchfeature/<name>buka Pull Requestgh pr creategate CI — build + typecheck+ preview PR · merge hanya kalau hijausquash-merge → mainsatu fitur = satu commit di maindeploy → Pages (live)
Satu fitur = satu Pull Request. Tiap PR mem-build dan typecheck; hanya merge hijau ke main yang menerbitkan situs.

Dua cara menyambungkan Pages

Ada dua cara mendapatkan deploy Cloudflare Pages. Keduanya jalan; kami pakai yang kedua, dan alasannya penting.

Cara mudah — integrasi Git native

Cara yang kami pakai — GitHub Actions

Repo sudah berisi .github/workflows/deploy.yml: ia menjalankan build + typecheck di tiap PR, dan men-deploy ke Pages hanya setelah Anda menambahkan dua secret repository. Sampai itu ada, ia tetap hijau tapi dorman — jadi Anda bisa pakai integrasi native sekarang dan beralih nanti.

File workflow-nya, dijelaskan

Seluruh pipeline ada di satu file yang sudah dibawa repo — .github/workflows/deploy.yml. Ini dia, sedikit dipangkas:

name: Deploy
on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

permissions:
  contents: read              # least-privilege: the job only reads the repo

jobs:
  build-deploy:
    runs-on: ubuntu-latest
    env:
      CF_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
    steps:
      - uses: actions/checkout@v6
      - uses: actions/setup-node@v6
        with:
          node-version: 20
          cache: npm
      - run: npm ci
      - run: npm run typecheck
      - name: Build
        run: npm run build      # prerender → dist/ + csp-hash.mjs (its last step)
        env:
          VITE_GA_ID: ${{ vars.VITE_GA_ID }}

      # Deploy ONLY on a push to main, and only once the token secret exists.
      - name: Deploy to Cloudflare Pages
        if: ${{ github.event_name == 'push' && env.CF_API_TOKEN != '' }}
        uses: cloudflare/wrangler-action@v4
        with:
          apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
          accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
          command: pages deploy apps/labs/dist --project-name=pangaea-id --branch=main

      # After a real deploy, ping IndexNow so Bing/Yandex recrawl within minutes.
      - name: Notify IndexNow (Bing/Yandex)
        if: ${{ github.event_name == 'push' && env.CF_API_TOKEN != '' }}
        continue-on-error: true
        run: npm run indexnow -w @pangaea/labs

Langkah demi langkah:

  • on: — job berjalan di tiap push ke main dan tiap pull request ke main.
  • permissions: contents: read — token GitHub job ini cuma bisa membaca repo; tak lebih.
  • env: CF_API_TOKEN — memunculkan secret sekali agar langkah deploy bisa menguji apakah ia terisi.
  • checkout + setup-node — clone repo dan pasang Node 20 dengan cache npm.
  • npm ci — install bersih sesuai lockfile (reproducible, beda dari npm install).
  • npm run typecheck — gate pertama. Error tipe menggagalkan job dan memblok PR.
  • Buildnpm run build — mem-prerender tiap halaman ke dist/ dan menjalankan csp-hash.mjs di langkah terakhir (alasan kami build di sini, bukan di container Cloudflare). VITE_GA_ID datang dari GitHub Actions Variable (publik by design; kalau kosong = GA4 tetap no-op).
  • Deploy … — gate yang menentukan: if: github.event_name == 'push' && env.CF_API_TOKEN != ''. Jadi PR mem-build + typecheck tapi tak pernah men-deploy, dan sebelum secret-nya ada langkah ini cuma dilewati (job tetap hijau). Saat ia jalan, wrangler-action mengunggah dist/ ke project Pages pangaea-id.
  • Notify IndexNow — gate yang sama, plus continue-on-error: true. Setelah deploy nyata ia menekan Bing/Yandex agar halaman baru/berubah di-crawl ulang dalam menit; kegagalan sesaat tak pernah merah-X deploy yang baik.

Rangkai deploy-nya: token → secret → Actions → Pages → domain

Ini jalur yang benar-benar dipakai situs. GitHub Actions mem-build (jadi csp-hash.mjs selalu jalan) dan mengunggah ke Pages dengan wrangler. Satu push ke main = satu deploy live.

Langkah 1 — Buat project Pages (sekali)

wrangler pages deploy tidak membuat project otomatis — ia error Project not found [8000007]. Buat sekali dulu. Bisa lewat dashboard (Workers & Pages → Create → Pages → Use direct upload, bukan "Connect to Git" → beri nama persis pangaea-id, yang harus cocok dengan --project-name di workflow → Create), atau dari CLI:

npx wrangler login   # membuka OAuth di browser, sekali saja
npx wrangler pages project create pangaea-id --production-branch=main

Langkah 2 — Buat token API hak-minimal

Profile → API Tokens → Create Token → Create Custom Token, persis:

  • Token namegithub-actions-pages-deploy
  • PermissionAccount · Cloudflare Pages · Edit (yang ini saja, tak ada lagi)
  • Account ResourcesInclude · akun Anda

Lalu Continue → Create, dan salin token sekarang — Cloudflare menampilkannya sekali saja.

Do

  • Buat Custom Token dengan satu permission Cloudflare Pages · Edit saja
  • Batasi ke satu akun Anda, dan salin nilainya segera

Don't

  • Memberi DNS / Zone / Workers / SSLpages deploy tak membutuhkannya, dan token sempit membatasi kerusakan kalau ia bocor
  • Memakai template jadi (mis. "Edit Cloudflare Workers" itu yang salah, terlalu luas)

Langkah 3 — Ambil Account ID Anda

Workers & Pages → sidebar kanan → Account ID (string hex 32 karakter).

Langkah 4 — Tambah dua secret GitHub

Repo → Settings → Secrets and variables → Actions → tab Secrets (bukan Variables) → New repository secret. Nama harus cocok persis:

  • CLOUDFLARE_API_TOKEN — token dari Langkah 2
  • CLOUDFLARE_ACCOUNT_ID — ID dari Langkah 3

Langkah 5 — Deploy

Push atau merge ke main. Pantau GitHub → Actions → Deploy: langkah "Deploy … to Cloudflare Pages" berubah dari skipped ke success, dan pangaea-id.pages.dev jadi live. Langkah terakhir "Notify IndexNow" lalu menekan Bing/Yandex agar halaman baru/berubah di-crawl ulang dalam menit — best-effort (continue-on-error), dan ia jalan hanya setelah deploy nyata.

Langkah 6 — Arahkan domain ke sana

Di Pages project → Custom domains → Set up a domain, tambahkan www.pangaea.id dan pangaea.id (ini menukar record parkir dengan yang proxied yang benar). Lalu tambahkan redirect apex → www 301 — lihat Bagian 3 · Root → www.

Contekan

Seluruh pipeline dalam satu baris:

merge PR → Actions build (npm run build → csp-hash) → wrangler pages deploy → npm run indexnow → live di www.pangaea.id dalam ~30d

Roll back kapan pun di Pages → Deployments → Rollback (instan, tanpa build ulang). Verifikasi rangkaiannya, read-only:

gh run list --branch main --limit 1      # run "Deploy" terbaru harusnya: success
curl -sI https://pangaea-id.pages.dev/   # 200 begitu project punya satu deployment

Troubleshooting: deploy pertama bilang "project not found"

npx wrangler login   # kalau belum login
npx wrangler pages project create pangaea-id --production-branch=main
npx wrangler pages deploy apps/labs/dist --project-name=pangaea-id

Tak yakin ia sudah ada? Daftar project Anda dulu — kalau pangaea-id ada, lewati langkah create dan langsung deploy (atau push ke main):

npx wrangler pages project list

Jangan

  • Terus mengulang deploy berharap berhasil. "Not found" berarti project-nya memang belum ada — buat sekali, lalu deploy.

Berikutnya

Pipeline ini merilis situs statis yang domainnya diarahkan Bagian 1. Cerita bahasa-sederhana kenapa Cloudflare Pages alih-alih VPS sewaan ada di catatan build kami, Rilis — Git → CI/CD → Pages; serah-terima DNS yang lebih dulu ada di Arahkan domain ke Cloudflare (DNS).

Sources

  1. cloudflare/wrangler-action — deploy dari GitHub Actions
  2. Cloudflare Pages — Git integration vs Direct Upload
  3. Cloudflare — permission token API (Pages: Edit)