Skip to content

Commit e050a7e

Browse files
authored
feat: support for more CDNs (#11)
1 parent bbd6d45 commit e050a7e

File tree

6 files changed

+78
-44
lines changed

6 files changed

+78
-44
lines changed

.changeset/afraid-kings-glow.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"cdn-cache-control": minor
3+
---
4+
5+
Adds support for Vercel, Akamai and Cloudflare headers

.changeset/dry-roses-flow.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"cdn-cache-control": patch
3+
---
4+
5+
Fix detection on Netlify

.changeset/mighty-guests-play.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"cdn-cache-control": minor
3+
---
4+
5+
Adds support for setting CDN via constructor argument

README.md

Lines changed: 31 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
11
<h1 align="center">cdn-cache-control</h1>
2-
<p align="center">
2+
<p align="center">
33
<img alt="cdn-cache-control" src="https://github.com/user-attachments/assets/62464d67-cbd5-48b5-a36d-800678ae22bb" />
44
</p>
55

6-
76
Easy, opinionated CDN cache header handling.
87

98
Modern CDNs allow very fine-grained control over the cache. This is particularly useful for server-side rendering of web content, as it allows you to manually handle the invalidation of content, ensuring it stays fast and fresh. This package provides a subclass of the `Headers` class that makes it easier to set cache control headers for content served through a modern CDN. It provides a simple, chainable API with sensible defaults for common use cases. It works by setting the `Cache-Control` and `CDN-Cache-Control` headers to the appropriate values. If run on a supported platform it will use the more specific header for that CDN. e.g. on Netlify it will use the `Netlify-CDN-Cache-Control` header.
@@ -33,6 +32,8 @@ The module exports a single class, `CacheHeaders`, which is a subclass of the fe
3332

3433
It can be instantiated with a `HeadersInit` value, which lets you base it on an existing `Headers` object, or an object or array with existing header values. In that case it will default to using existing `s-maxage` directives if present.
3534

35+
You can pass a `cdn` value as the second argument to set the CDN cache control header. In some cases this will enable targeted cache header names. e.g. for Netlify it will use the `Netlify-CDN-Cache-Control` header. Currently supported values are `netlify`, `vercel`, `cloudflare` and `akamai`. If you don't pass a value, or pass an unsupported one it will use the generic `CDN-Cache-Control` header. It will also attempt to detect the platform automatically on Vercel and Netlify.
36+
3637
### Use cases
3738

3839
If you have content that you want to have the CDN cache until it is manually revalidated or purged with a new deploy, you can use the default values:
@@ -178,18 +179,34 @@ Number of seconds in one year
178179

179180
### Methods
180181

181-
- [tag](#gear-tag)
182-
- [swr](#gear-swr)
183-
- [immutable](#gear-immutable)
184-
- [ttl](#gear-ttl)
185-
- [toObject](#gear-toobject)
186-
- [copyTo](#gear-copyto)
187-
- [getCdnCacheControl](#gear-getcdncachecontrol)
188-
- [setCdnCacheControl](#gear-setcdncachecontrol)
189-
- [getCacheControl](#gear-getcachecontrol)
190-
- [setCacheControl](#gear-setcachecontrol)
191-
- [getCacheTags](#gear-getcachetags)
192-
- [setCacheTags](#gear-setcachetags)
182+
- [Installation](#installation)
183+
- [Usage](#usage)
184+
- [Use cases](#use-cases)
185+
- [stale-while-revalidate](#stale-while-revalidate)
186+
- [Immutable content](#immutable-content)
187+
- [Cache tags](#cache-tags)
188+
- [Using the generated headers](#using-the-generated-headers)
189+
- [API](#api)
190+
- [:wrench: Constants](#wrench-constants)
191+
- [:gear: ONE\_MINUTE](#gear-one_minute)
192+
- [:gear: ONE\_HOUR](#gear-one_hour)
193+
- [:gear: ONE\_DAY](#gear-one_day)
194+
- [:gear: ONE\_WEEK](#gear-one_week)
195+
- [:gear: ONE\_YEAR](#gear-one_year)
196+
- [:factory: CacheHeaders](#factory-cacheheaders)
197+
- [Methods](#methods)
198+
- [:gear: tag](#gear-tag)
199+
- [:gear: swr](#gear-swr)
200+
- [:gear: immutable](#gear-immutable)
201+
- [:gear: ttl](#gear-ttl)
202+
- [:gear: toObject](#gear-toobject)
203+
- [:gear: copyTo](#gear-copyto)
204+
- [:gear: getCdnCacheControl](#gear-getcdncachecontrol)
205+
- [:gear: setCdnCacheControl](#gear-setcdncachecontrol)
206+
- [:gear: getCacheControl](#gear-getcachecontrol)
207+
- [:gear: setCacheControl](#gear-setcachecontrol)
208+
- [:gear: getCacheTags](#gear-getcachetags)
209+
- [:gear: setCacheTags](#gear-setcachetags)
193210

194211
#### :gear: tag
195212

@@ -300,15 +317,3 @@ The parsed content of the cache tags header.
300317
| `setCacheTags` | `(tags: string[]) => void` |
301318

302319
<!-- TSDOC_END -->
303-
304-
```
305-
306-
```
307-
308-
```
309-
310-
```
311-
312-
```
313-
314-
```

netlify.test.js

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,19 +3,16 @@ import { before, describe, it } from "node:test";
33
import { CacheHeaders } from "./dist/index.js";
44

55
describe("Netlify", () => {
6-
before(() => {
7-
process.env.NETLIFY = "true";
8-
});
96
it("sets tiered header on Netlify", () => {
10-
const headers = new CacheHeaders().swr();
7+
const headers = new CacheHeaders(undefined, "netlify").swr();
118
assert.strictEqual(
129
headers.get("Netlify-CDN-Cache-Control"),
1310
"public,s-maxage=0,durable,stale-while-revalidate=604800",
1411
);
1512
});
1613

1714
it("should detect Netlify CDN", () => {
18-
const headers = new CacheHeaders().immutable();
15+
const headers = new CacheHeaders(undefined, "netlify").immutable();
1916
assert(headers.has("Netlify-CDN-Cache-Control"));
2017
});
2118
});

src/index.ts

Lines changed: 30 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,13 @@
11
import process from "node:process";
22

33
// TODO: Add more CDNs
4-
/** The CDN that the cache headers are being used with. WIll work with other CDNs, but may miss platform-specific headers and directives. */
5-
export type CDN = "netlify";
4+
/** The CDN that the cache headers are being used with. Will work with other CDNs, but may miss platform-specific headers and directives. */
5+
export type CDN =
6+
| "netlify"
7+
| "cloudflare"
8+
| "akamai"
9+
| "vercel"
10+
| (string & {});
611

712
/** Number of seconds in one minute */
813
export const ONE_MINUTE = 60;
@@ -18,13 +23,28 @@ export const ONE_YEAR = 31536000;
1823
// The tiered directive is used by Netlify to indicate that it should use a tiered cache, with a central cache shared by all edge nodes.
1924
const tieredDirective = "durable";
2025

26+
const cdnCacheControlHeaderNames = new Map<CDN, string>([
27+
["netlify", "Netlify-CDN-Cache-Control"],
28+
["cloudflare", "Cloudflare-CDN-Cache-Control"],
29+
["akamai", "Akamai-Cache-Control"],
30+
["vercel", "Vercel-CDN-Cache-Control"],
31+
]);
32+
2133
function detectCDN(): CDN | undefined {
22-
if (process.env.NETLIFY || process.env.NETLIFY_LOCAL) {
23-
return "netlify";
24-
}
2534
if (process.env.CDN) {
2635
return process.env.CDN as CDN;
2736
}
37+
if (process.env.VERCEL) {
38+
return "vercel";
39+
}
40+
if (
41+
process.env.NETLIFY ||
42+
process.env.NETLIFY_LOCAL ||
43+
process.env.NETLIFY_BLOBS_CONTEXT
44+
) {
45+
return "netlify";
46+
}
47+
2848
return undefined;
2949
}
3050

@@ -57,9 +77,9 @@ function serializeCacheControlHeader(
5777
export class CacheHeaders extends Headers {
5878
#cdn?: CDN;
5979

60-
public constructor(init?: HeadersInit) {
80+
public constructor(init?: HeadersInit, cdn?: CDN) {
6181
super(init);
62-
this.#cdn = detectCDN();
82+
this.#cdn = cdn ?? detectCDN();
6383
const cdnDirectives = parseCacheControlHeader(
6484
this.get(this.cdnCacheControlHeaderName),
6585
);
@@ -194,12 +214,9 @@ export class CacheHeaders extends Headers {
194214
}
195215

196216
private get cdnCacheControlHeaderName(): string {
197-
switch (this.#cdn) {
198-
case "netlify":
199-
return "Netlify-CDN-Cache-Control";
200-
default:
201-
return "CDN-Cache-Control";
202-
}
217+
return (
218+
cdnCacheControlHeaderNames.get(this.#cdn ?? "") ?? "CDN-Cache-Control"
219+
);
203220
}
204221

205222
/**

0 commit comments

Comments
 (0)