Skip to content

GitHub Actions

For a site whose build runs in GitHub Actions and is uploaded to a host from there (Cloudflare Pages direct upload, GitHub Pages, S3, a server). There is no build hook to make, so you add a small endpoint that starts the workflow. Each publish is then one run of the workflow, usually a minute or two of Actions time.

The example endpoint runs as a Cloudflare Pages Function. Any host that runs a small function (Netlify Functions, Vercel Functions, a Worker) works the same way.

  1. A GitHub token for the endpoint. GitHub → your picture → Settings → Developer settings → Personal access tokens → Fine-grained tokens → Generate new token.

    • Resource owner: the account or organisation that owns the repository.
    • Repository access: Only select repositories → the site’s repository.
    • Repository permissions → Actions: Read and write. Nothing else (Metadata: read is added by itself).
    • Note the expiry date: after it, the endpoint answers 502 and the owner’s card says the rebuild was refused.
  2. Two secrets where the endpoint runs. On Cloudflare: Workers & Pages → the project → Settings → Variables and secrets → Add, type Secret, for Production:

    • GITHUB_DISPATCH_TOKEN = the token.
    • REBUILD_HOOK_SECRET = a long random value, for example from openssl rand -hex 32. Keep it for step 5.
  3. The feed values in GitHub. The repository → Settings → Secrets and variables → Actions:

    • Secrets → New repository secret: JOOGOOS_FEED_KEY = the key.
    • Variables → New repository variable: JOOGOOS_FEED_URL = the feed address.

    Add both together. A build with one and not the other should stop with a clear error.

  4. The workflow must accept a manual start and pass both values to the build (below).

  5. The hook address, for the owner to paste in Joogoos (Business → SEO → Where they are published → Build hook → Save): https://<your site>/api/rebuild/<REBUILD_HOOK_SECRET>

  6. Deploy once (run the workflow by hand): Cloudflare reads new secrets only from the next deployment. Then check:

    Terminal window
    curl -X POST -o /dev/null -w "%{http_code}\n" https://<your site>/api/rebuild/wrong # 404
    curl -X POST -o /dev/null -w "%{http_code}\n" https://<your site>/api/rebuild/<secret> # 202, and a run starts
// functions/api/rebuild/[token].js — a Cloudflare Pages Function
export async function onRequestPost({ params, env }) {
// A wrong address looks like no address at all.
if (!env.REBUILD_HOOK_SECRET || params.token !== env.REBUILD_HOOK_SECRET) return new Response(null, { status: 404 });
const res = await fetch('https://api.github.com/repos/OWNER/REPO/actions/workflows/site.yml/dispatches', {
method: 'POST',
headers: {
authorization: `Bearer ${env.GITHUB_DISPATCH_TOKEN}`,
accept: 'application/vnd.github+json',
'user-agent': 'blog-rebuild',
},
body: JSON.stringify({ ref: 'main' }),
});
return new Response(null, { status: res.ok ? 202 : 502 });
}

Replace OWNER/REPO and site.yml with yours. Joogoos rings at most once per publish, thirty seconds after it, and groups several publishes into one ring. If you want a hard ceiling on runs, count them in the endpoint (for example with the GitHub API’s list of today’s runs) and answer 429 past it.

.github/workflows/site.yml
on:
push:
branches: [main]
workflow_dispatch: # the endpoint starts it
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with: { node-version: 22 }
- run: npm ci
- run: npm run build
env:
JOOGOOS_FEED_URL: ${{ vars.JOOGOOS_FEED_URL }}
JOOGOOS_FEED_KEY: ${{ secrets.JOOGOOS_FEED_KEY }}
# …then upload, e.g. cloudflare/wrangler-action with `pages deploy dist`

The build reads the feed exactly as on Netlify.