Astro image caching
Astro has some great built-in image optimization features. You may have noticed when running Astro builds locally that image optimizations are cached in the node_modules/.astro
directory. This is great for local development, but when running builds in GitHub Actions, the node_modules/.astro
directory is not cached by default. This means that every time you run a build in GitHub Actions, Astro will re-optimize all of your images, which can take a long time.
My relatively small blog currently generates 564 unique images! This can take upwards of 20 minutes, on the free-tier GitHub Action Runners, to generate/optimize all images without caching.

With caching (on my machine), images are gathered in around 300ms. We can achieve similar performance within GitHub Actions if we utilize the cache properly.
Caching in GitHub Actions
GitHub provides actions/cache
, actions/cache/restore
, and actions/cache/save
on the Marketplace. By adjusting the location of the Astro cache and making use of the restore/save actions, we can cache image optimizations between runs.
A few important things to know about the GitHub Actions Cache:
- The cache exists on a per-repo basis and additionally on a per-branch basis. If a restore is attempted, it will first attempt to load the branch-specific cache and then the default branch cache as a backup.
- The total size of a repo’s cache cannot exceed 10GB. Once exceeded, the oldest cache will be purged.
- Caches are purged after 7 days if not accessed.
Example
The below snippets can be seen in a complete example from this repo: danwulff/astro-image-cache.
Here, I’ve updated the Astro config to use a custom cache directory. I had some issues with the default node_modules/.astro
directory:
import { defineConfig } from 'astro/config';
export default defineConfig({
cacheDir: './cache',
});
This Astro “Build” workflow includes these steps in order to restore existing caches and save new ones after a successful build: “Restore cached images”, “Check if images cache exists for current branch”, “Clear existing images cache”, and “Cache images”. Below the snippet, you’ll find a quick description of each.
name: Build
on:
push:
branches:
- main
pull_request:
branches:
- main
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
permissions:
contents: read
actions: write
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: lts/*
cache: npm
- name: Restore cached images
id: cache-images-restore
uses: actions/cache/restore@v4
with:
path: cache
key: Images-${{ runner.os }}
- name: Install
run: npm ci --fund=false --progress=false
- name: Build prod
run: npm run build
- name: Install gh actions-cache extension
run: gh extension install actions/gh-actions-cache
env:
GH_TOKEN: ${{ github.token }}
- name: Check if images cache exists for current branch
id: actions-cache-list
env:
GH_TOKEN: ${{ github.token }}
run: echo "RESULT=$(gh actions-cache list --branch ${{ github.ref }} --key Images-)" >> $GITHUB_OUTPUT
- name: Clear existing images cache
if: ${{ steps.actions-cache-list.outputs.RESULT != '' }}
env:
GH_TOKEN: ${{ github.token }}
run: gh actions-cache delete ${{ steps.cache-images-restore.outputs.cache-primary-key }} --branch ${{ github.ref }} --confirm
- name: Cache images
uses: actions/cache/save@v4
with:
path: cache
key: ${{ steps.cache-images-restore.outputs.cache-primary-key }}
Description:
- Restore cached images: This step will attempt to restore the cache from the previous run. If it doesn’t exist, it will continue without error.
- Check if images cache exists for the current branch: This step will check if a cache exists for the current branch. If it does, the next step will delete it.
- Clear existing images cache: This step will delete the existing cache for the current branch. Attempting to save a cache to an existing key will throw an error, thus a deletion is necessary. Note: we can’t use the output from
cache-images-restore
to determine whether to run this step, as a cache hit will still occur on a branch if the default branch has a cache available. - Cache images: This step will save the cache for the current branch.
Because GitHub’s Action Cache will expire after 7 days, we need to run a separate workflow to keep the cache hot. This is optional but could be useful if your project often has periods of inactivity. Note: this only keeps the cache hot for the default branch of the repo (which is likely good enough as the default branch cache is the fallback for all branches).
name: Keep images cache hot
on:
schedule:
- cron: 0 6 * * 6 # Every Saturday at 6:00 AM
jobs:
hit-cache:
runs-on: ubuntu-latest
steps:
- name: Restore cached images
id: cache-images-restore
uses: actions/cache/restore@v4
with:
path: cache
key: Images-${{ runner.os }}