TIL: Caching in GitHub Actions

Last updated: 5/19/2025

Recently, I moved this site (yes, this very one!) from Cloudflare Pages to Cloudflare Workers (on which more below). So I no longer benefit from Cloudflare Page’s built-in one-click deploys — I have to deploy myself via a GitHub Action.

That was mostly very easy — run an npm run build followed by the official cloudflare/wrangler-action.1 But one complication came from Astro.

Astro optimizes images by default, which can take quite long (on the order of 5 websites for a site of my size). Luckily, it has image caching in between builds. Unluckily, GitHub Actions does not support this by default, as Cloudflare Pages did — I had to set it up manually. Neither luckily nor unluckily, there’s an official actions/cache action that can support this workflow.

However, actions/cache was a bit of a hassle to get working, since we want to invalidate the cache when new images are added. I read one post that recommended using the GitHub CLI within the workflow to delete the cache each time, but I couldn’t get that working — the GitHub CLI returned a permission error every time I tried.

I was stuck on this a bit, but eventually learned there’s a hashFiles function in GitHub Actions. That allows using the cache “correctly” — builds share a cache key (with the same hash value) only if no files have been added. Astro optimizes any image files in src/assets by default, so I just hash all of those.

One last thing: for whatever reason, I couldn’t get the default /dist/.astro cache directory to work. Inspired by the blog post above, I configured Astro to use cache/ instead, and it worked fine 🤷‍♀️

Here’s the final workflow:

name: Deploy
on:
  push:
    branches:
      - main
jobs:
  deploy:
    runs-on: ubuntu-latest
    timeout-minutes: 60
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: 'npm'
      - name: Restore cached images
        uses: actions/cache@v4
        with:
          path: cache
          key: _astro-${{ hashFiles('src/assets/**/*.jpg', 'src/assets/**/*.jpeg', 'src/assets/**/*.webp', 'src/assets/**/*.webp') }}
      - run: npm ci
      - run: npm run build
      - name: Build & Deploy Site
        uses: cloudflare/wrangler-action@v3
        with:
          apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
          accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
          wranglerVersion: "4.14.4"

Aside: Porting to Workers

Workers has support for fully-static websites now via Static Assets, with the exact same limits as Pages, and Cloudflare is quietly pushing folks to switch. So I figured I’d get it out of the way. Luckily, with the latest version of Workers, it was extremely easy.

You just have to add a wrangler.jsonc that defines where to find the Static Assets and any custom routes you want to serve the site at. Here’s mine:

{
  "name": "rwblickhanorg",
  "compatibility_date": "2025-05-14",
  "assets": {
    "directory": "./dist",
  },
  "routes": [
    {
      "pattern": "rwblickhan.org",
      "custom_domain": true,
    },
  ],
}

Then just run npx wrangler@latest deploy to deploy! (Or use the GitHub Action above.)

Footnotes

  1. Do note the specified wranglerVersion. Static Assets is pretty new — deploys failed with the older default version of Wrangler.

Reply by email!