# Caching Layers

This page explains the cache key, lifetime, and invalidation of each of the four layers an image passes through: browser, Cloudflare Worker, Nginx, and local cache files.

## The Four Layers

| Layer | Cache key | Lifetime | Configured in |
|---|---|---|---|
| Browser | Full URL | 7 days (`Cache-Control: public, max-age=604800` plus `Expires`) | `GetFromPath` headers; Nginx adds the same value |
| Cloudflare Worker | Origin URL plus full query string | 7 days (`max-age=604800`) | `worker/index.js` |
| Nginx | Default `proxy_cache` key (includes query) | 7 days for 200 / 301 / 302 / 304, 1 minute otherwise; evicted after 30 days without access; 2 GB cap | `config/nginx/nodes.conf` |
| Local cache file | Filename built from parameters (below) | No expiry, never cleaned automatically | `GetFileCachePath()` |

## Local Cache File Naming

Cache files live in `storage/image/cache/`, mirroring `upload/`. The original extension is stripped and the parameters are appended:

| Parameters | Filename pattern |
|---|---|
| `s` present | `{name}_{s}_{q}_{b}_{B}.{t}` |
| `w` and `h` | `{name}_{w}_{h}_{q}_{b}_{B}.{t}` |
| Only `w` | `{name}_{w}_auto_{q}_{b}_{B}.{t}` |
| Only `h` | `{name}_auto_{h}_{q}_{b}_{B}.{t}` |
| None | `{name}_auto_auto_{q}_{b}_{B}.{t}` |

Each segment uses the **raw request string**, unnormalized: `?s=480&t=avif` yields `{name}_480___1.avif` (`q` and `b` are empty, `B` defaults to `1`), while `?s=480&t=avif&q=50` produces identical output but a separate cache file.

## Lookup Order

```mermaid
graph LR
    Req[Request] --> B{Browser fresh?}
    B -- No --> W{Worker hit?}
    W -- No --> N{Nginx hit?}
    N -- No --> L{Local cache file exists?}
    L -- No --> V[libvips transform and write cache file]
```

Requests that bypass the Worker go from the browser straight to Nginx. `.pdf`, `.svg`, and `o=1` never write a local cache file and are cached only by the first three layers.

## Invalidation

The code has no cache purge. Deleting an original only moves the file in `upload/` to the trash; transformed results in `cache/` remain, and `GetFromPath` checks the cache file before the original, so **existing size variants of a deleted image keep being served**. To take an image down immediately, delete the matching files under `storage/image/cache/` and purge both the Nginx cache at `/var/cache/nginx/images` and the Cloudflare cache.
