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_KEYsecret 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:
- Commit the assets. Run the Action on
workflow_dispatchorschedule, let it open a PR with new assets. Your prod build is deterministic. - 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.