Skip to content

Commit fa67c65

Browse files
authored
Merge pull request #927 from AvaloniaUI/update-caching
Implement 5-min client caching and 7-day edge caching with purge on deploy
2 parents 5863e40 + 205b63b commit fa67c65

File tree

5 files changed

+61
-7
lines changed

5 files changed

+61
-7
lines changed

.github/workflows/docs-avaloniaui-net.yml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,3 +130,14 @@ jobs:
130130
npx wrangler kv bulk put redirects/kv-redirects.json \
131131
--namespace-id ae8131374b0d409e9ab0537882dafe0b \
132132
--remote
133+
134+
- name: Purge Cloudflare edge cache
135+
if: github.ref == 'refs/heads/main' && github.event_name != 'pull_request'
136+
env:
137+
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
138+
run: |
139+
curl -sf -X POST \
140+
"https://api.cloudflare.com/client/v4/zones/aaa9cb3f1e40e56edad4ceba5226d0e0/purge_cache" \
141+
-H "Authorization: Bearer $CLOUDFLARE_API_TOKEN" \
142+
-H "Content-Type: application/json" \
143+
--data '{"hosts":["docs.avaloniaui.net"]}'

README.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,20 @@ If you want API reference content to regenerate automatically before every local
105105

106106
Do not commit these hooks, as CI does not have `dotnet-apiref` installed and the build will fail. The generated API reference files are already committed to the repository.
107107

108+
## Caching
109+
110+
The site is served through a Cloudflare Worker ([`workers/docs-redirects`](workers/docs-redirects)) which handles redirects, trailing-slash normalization, and edge caching.
111+
112+
| Content | Edge (Cloudflare) | Browser |
113+
|---|---|---|
114+
| Hashed assets (`/assets/*`) | 1 year | 1 year, immutable |
115+
| Other static files (`/img/*`, etc.) | Respects origin headers | 5 min |
116+
| HTML pages | 7 days | 5 min |
117+
118+
The edge cache for `docs.avaloniaui.net` is automatically purged on every deploy to `main`, so content updates are served immediately.
119+
120+
See [`workers/docs-redirects/README.md`](workers/docs-redirects/README.md) for full details.
121+
108122
## Thanks 💜
109123

110124
Thanks for all your contributions and efforts towards improving the Avalonia UI documentation. We thank you being part of our ✨ community ✨!

redirects/cloudflare_worker_kv_redirects/README.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,9 +43,12 @@ This produces `kv-redirects.json` (gitignored).
4343

4444
```bash
4545
npx wrangler kv bulk put kv-redirects.json \
46-
--namespace-id ae8131374b0d409e9ab0537882dafe0b
46+
--namespace-id ae8131374b0d409e9ab0537882dafe0b \
47+
--remote
4748
```
4849

50+
> **Note:** Wrangler v4 defaults to local KV. The `--remote` flag is required to write to the production KV namespace.
51+
4952
## CI/CD
5053

5154
KV entries are automatically regenerated and uploaded on every deploy to `main` via the `docs.avaloniaui.net CI/CD` GitHub Actions workflow (`.github/workflows/docs-avaloniaui-net.yml`). A validation step checks that all redirect targets point to allowed domains (`docs.avaloniaui.net`, `github.com/AvaloniaUI`) before uploading.

workers/docs-redirects/README.md

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,16 @@ Cloudflare Worker that handles server-side redirects and trailing-slash normaliz
66

77
1. **KV redirects** — Looks up the request path in the `DOCS_REDIRECTS` KV namespace. If a match is found, returns a `301` redirect to the target URL.
88
2. **Trailing-slash rewrite** — If no redirect is found and the path looks like a page (no file extension), the Worker internally rewrites the request to append a trailing slash. S3-compatible object storage (e.g. AWS, Scaleway) always returns a `302` redirect to the trailing-slash version when a path is requested without one (e.g. `/docs/welcome``302``/docs/welcome/`). This rewrite eliminates that round-trip by fetching the trailing-slash path internally, so the browser gets the page directly.
9-
3. **Static asset bypass** — Requests for static assets (`.js`, `.css`, `.png`, etc. and `/assets/*` paths) skip KV lookup entirely and pass straight through to origin.
9+
3. **Edge caching** — HTML pages are cached at the Cloudflare edge for 7 days (`cacheTtl: 604800`, `cacheEverything: true`). Browsers receive `Cache-Control: public, max-age=300` (5 minutes). The edge cache is purged by hostname on every deploy via the CI/CD workflow, so new content is served immediately after a push to `main`.
10+
4. **Static asset bypass** — Requests for static assets (`.js`, `.css`, `.png`, etc. and `/assets/*` paths) skip KV lookup and edge caching, passing straight through to origin (Cloudflare caches these by default based on file extension).
1011

1112
## Request flow
1213

1314
```
1415
Browser request
1516
1617
17-
Static asset? ──YES──▶ passthrough to origin
18+
Static asset? ──YES──▶ passthrough to origin (cached by file extension)
1819
1920
NO
2021
@@ -25,13 +26,24 @@ Browser request
2526
2627
NOT FOUND
2728
28-
Needs trailing slash? ──YES──▶ rewrite: fetch(path/) internally
29+
Needs trailing slash? ──YES──▶ rewrite: fetch(path/) with edge cache
2930
3031
NO
3132
32-
passthrough to origin
33+
fetch with edge cache ──▶ origin (7-day edge TTL, 5-min browser TTL)
3334
```
3435

36+
## Caching strategy
37+
38+
| Content | Edge (Cloudflare) | Browser | Set by |
39+
|---|---|---|---|
40+
| Hashed assets (`/assets/*`) | 1 year | 1 year, immutable | Origin `Cache-Control` header (set during S3 upload) |
41+
| Other static files (`/img/*`, etc.) | Cloudflare default (respects origin headers) | 5 min | Origin `Cache-Control` header (set during S3 upload) |
42+
| HTML pages | 7 days | 5 min | Worker `cf` fetch options + `withBrowserCache` |
43+
| KV redirects (301s) | N/A (generated by Worker) | 24 hours | Worker response header |
44+
45+
The edge cache for `docs.avaloniaui.net` is purged by hostname on every deploy to `main` via the CI/CD workflow, so new content is available immediately regardless of edge TTL.
46+
3547
## Configuration
3648

3749
- **Route**: `docs.avaloniaui.net/*`

workers/docs-redirects/src/index.ts

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,11 @@ export interface Env {
44

55
const STATIC_EXT = /\.(?:js|css|png|jpg|jpeg|gif|svg|ico|woff|woff2|ttf|eot|map|json|xml|txt|webp|avif|mp4|webm)$/i;
66

7+
const EDGE_CACHE: RequestInitCfProperties = {
8+
cacheTtl: 604800,
9+
cacheEverything: true,
10+
};
11+
712
function isStaticAsset(path: string): boolean {
813
return path.startsWith('/assets/') || path.startsWith('/img/') || STATIC_EXT.test(path);
914
}
@@ -59,10 +64,19 @@ export default {
5964
const rewrittenUrl = new URL(request.url);
6065
rewrittenUrl.pathname = url.pathname + '/';
6166
const rewrittenRequest = new Request(rewrittenUrl.toString(), request);
62-
return fetch(rewrittenRequest);
67+
return withBrowserCache(await fetch(rewrittenRequest, { cf: EDGE_CACHE }));
6368
}
6469

6570
// Default: passthrough to origin
66-
return fetch(request);
71+
return withBrowserCache(await fetch(request, { cf: EDGE_CACHE }));
6772
},
6873
};
74+
75+
function withBrowserCache(response: Response): Response {
76+
if (response.headers.has('Cache-Control')) {
77+
return response;
78+
}
79+
const newResponse = new Response(response.body, response);
80+
newResponse.headers.set('Cache-Control', 'public, max-age=300');
81+
return newResponse;
82+
}

0 commit comments

Comments
 (0)