Cookbook

Recipe 6: GitHub Action that fetches assets at build time

JAMstack / static-site pattern — webfetch runs in CI, commits fresh license-safe assets into your repo on every build.

You run a static site (Astro, Next.js SSG, Hugo). You want fresh hero images for dynamic pages, fetched at build time, with attribution baked in. This pattern keeps runtime fast and keeps licensing out of your runtime path.

Try it: drop a WEBFETCH_API_KEY secret into your repo — free at app.getwebfetch.com/signup — and the Action below runs without any per-provider auth.

#The Action

# .github/workflows/build-assets.yml
name: build

on:
  push: { branches: [main] }
  schedule:
    - cron: "0 4 * * *"

jobs:
  assets:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        asset:
          - { query: "minimalist mountain sunrise", out: "homepage-hero" }
          - { query: "modern co-working space", out: "about-page" }
          - { query: "reading glasses on book", out: "blog-latest" }
    steps:
      - uses: actions/checkout@v4

      - name: Fetch license-safe assets
        uses: ashlrai/webfetch/integrations/github-action@main
        with:
          query: ${{ matrix.asset.query }}
          out-dir: ./public/generated/${{ matrix.asset.out }}
          license: safe-only
          min-width: 1600
          providers: wikimedia,openverse,unsplash,pexels
        env:
          UNSPLASH_ACCESS_KEY: ${{ secrets.UNSPLASH_ACCESS_KEY }}
          PEXELS_API_KEY: ${{ secrets.PEXELS_API_KEY }}

      - name: Build site
        run: npm ci && npm run build

      - name: Deploy
        uses: cloudflare/pages-action@v1
        with:
          apiToken: ${{ secrets.CF_API_TOKEN }}
          projectName: my-site
          directory: ./out

#Output

For each matrix row, the Action writes downloaded files plus _manifest.json under ./public/generated/<slug>/:

[
  {
    "file": "./public/generated/homepage-hero/001.jpg",
    "sha256": "...",
    "candidate": {
      "url": "https://images.unsplash.com/...",
      "license": "CC0",
      "author": "Jane Photog",
      "attributionLine": "Photo by Jane Photog on Unsplash",
      "confidence": 0.85
    }
  }
]

#Render the credit

---
// src/pages/index.astro
import manifest from "../../public/generated/homepage-hero/_manifest.json";
const first = manifest[0];
---
<img src={`/generated/homepage-hero/${first.file.split("/").at(-1)}`} alt="" />
<small class="credit">{first.candidate.attributionLine}</small>

Adjust the import paths if you rename the generated files after download.

#Committing vs not committing

Two patterns:

  1. Commit the assets. Run the Action on workflow_dispatch or schedule, let it open a PR with new assets. Your prod build is deterministic.
  2. Don't commit. Fetch at build time on every deploy. Simpler, but your deploys depend on upstream provider availability. Mitigate with aggressive caching.

Pattern 1 is the safer default.