@@ -18,6 +18,14 @@ type Options = {
1818 */
1919 mode : "short-lived" | "long-lived" ;
2020
21+ /**
22+ * The default TTL of long-lived cache entries.
23+ * When no revalidate is provided, the default age will be used.
24+ *
25+ * @default `THIRTY_MINUTES_IN_SECONDS`
26+ */
27+ defaultLongLivedTtlSec ?: number ;
28+
2129 /**
2230 * Whether the regional cache entry should be updated in the background or not when it experiences
2331 * a cache hit.
@@ -27,6 +35,12 @@ type Options = {
2735 shouldLazilyUpdateOnCacheHit ?: boolean ;
2836} ;
2937
38+ interface PutToCacheInput {
39+ key : string ;
40+ isFetch : boolean | undefined ;
41+ entry : IncrementalCacheEntry < boolean > ;
42+ }
43+
3044/**
3145 * Wrapper adding a regional cache on an `IncrementalCache` implementation
3246 */
@@ -52,10 +66,10 @@ class RegionalCache implements IncrementalCache {
5266 ) : Promise < WithLastModified < CacheValue < IsFetch > > | null > {
5367 try {
5468 const cache = await this . getCacheInstance ( ) ;
55- const localCacheKey = this . getCacheKey ( key , isFetch ) ;
69+ const urlKey = this . getCacheUrlKey ( key , isFetch ) ;
5670
5771 // Check for a cached entry as this will be faster than the store response.
58- const cachedResponse = await cache . match ( localCacheKey ) ;
72+ const cachedResponse = await cache . match ( urlKey ) ;
5973 if ( cachedResponse ) {
6074 debugCache ( "Get - cached response" ) ;
6175
@@ -66,7 +80,7 @@ class RegionalCache implements IncrementalCache {
6680 const { value, lastModified } = rawEntry ?? { } ;
6781
6882 if ( value && typeof lastModified === "number" ) {
69- await this . putToCache ( localCacheKey , { value, lastModified } ) ;
83+ await this . putToCache ( { key , isFetch , entry : { value, lastModified } } ) ;
7084 }
7185 } )
7286 ) ;
@@ -80,7 +94,7 @@ class RegionalCache implements IncrementalCache {
8094 if ( ! value || typeof lastModified !== "number" ) return null ;
8195
8296 // Update the locale cache after retrieving from the store.
83- getCloudflareContext ( ) . ctx . waitUntil ( this . putToCache ( localCacheKey , { value, lastModified } ) ) ;
97+ getCloudflareContext ( ) . ctx . waitUntil ( this . putToCache ( { key , isFetch , entry : { value, lastModified } } ) ) ;
8498
8599 return { value, lastModified } ;
86100 } catch ( e ) {
@@ -97,11 +111,15 @@ class RegionalCache implements IncrementalCache {
97111 try {
98112 await this . store . set ( key , value , isFetch ) ;
99113
100- await this . putToCache ( this . getCacheKey ( key , isFetch ) , {
101- value,
102- // Note: `Date.now()` returns the time of the last IO rather than the actual time.
103- // See https://developers.cloudflare.com/workers/reference/security-model/
104- lastModified : Date . now ( ) ,
114+ await this . putToCache ( {
115+ key,
116+ isFetch,
117+ entry : {
118+ value,
119+ // Note: `Date.now()` returns the time of the last IO rather than the actual time.
120+ // See https://developers.cloudflare.com/workers/reference/security-model/
121+ lastModified : Date . now ( ) ,
122+ } ,
105123 } ) ;
106124 } catch ( e ) {
107125 error ( `Failed to get from regional cache` , e ) ;
@@ -113,7 +131,7 @@ class RegionalCache implements IncrementalCache {
113131 await this . store . delete ( key ) ;
114132
115133 const cache = await this . getCacheInstance ( ) ;
116- await cache . delete ( this . getCacheKey ( key ) ) ;
134+ await cache . delete ( this . getCacheUrlKey ( key ) ) ;
117135 } catch ( e ) {
118136 error ( "Failed to delete from regional cache" , e ) ;
119137 }
@@ -126,27 +144,36 @@ class RegionalCache implements IncrementalCache {
126144 return this . localCache ;
127145 }
128146
129- protected getCacheKey ( key : string , isFetch ?: boolean ) {
130- return new Request (
131- new URL (
132- `${ process . env . NEXT_BUILD_ID ?? FALLBACK_BUILD_ID } /${ key } .${ isFetch ? "fetch" : "cache" } ` ,
133- "http://cache.local"
134- )
147+ protected getCacheUrlKey ( key : string , isFetch ?: boolean ) {
148+ const buildId = process . env . NEXT_BUILD_ID ?? FALLBACK_BUILD_ID ;
149+ return (
150+ "http://cache.local" + `/${ buildId } /${ key } ` . replace ( / \/ + / g, "/" ) + `.${ isFetch ? "fetch" : "cache" } `
135151 ) ;
136152 }
137153
138- protected async putToCache ( key : Request , entry : IncrementalCacheEntry < boolean > ) : Promise < void > {
154+ protected async putToCache ( { key, isFetch, entry } : PutToCacheInput ) : Promise < void > {
155+ const urlKey = this . getCacheUrlKey ( key , isFetch ) ;
139156 const cache = await this . getCacheInstance ( ) ;
140157
141158 const age =
142159 this . opts . mode === "short-lived"
143160 ? ONE_MINUTE_IN_SECONDS
144- : entry . value . revalidate || THIRTY_MINUTES_IN_SECONDS ;
161+ : entry . value . revalidate || this . opts . defaultLongLivedTtlSec || THIRTY_MINUTES_IN_SECONDS ;
145162
163+ // We default to the entry key if no tags are found.
164+ // so that we can also revalidate page router based entry this way.
165+ const tags = getTagsFromCacheEntry ( entry ) ?? [ key ] ;
146166 await cache . put (
147- key ,
167+ urlKey ,
148168 new Response ( JSON . stringify ( entry ) , {
149- headers : new Headers ( { "cache-control" : `max-age=${ age } ` } ) ,
169+ headers : new Headers ( {
170+ "cache-control" : `max-age=${ age } ` ,
171+ ...( tags . length > 0
172+ ? {
173+ "cache-tag" : tags . join ( "," ) ,
174+ }
175+ : { } ) ,
176+ } ) ,
150177 } )
151178 ) ;
152179 }
@@ -169,9 +196,33 @@ class RegionalCache implements IncrementalCache {
169196 * or an ISR/SSG entry for up to 30 minutes.
170197 * @param opts.shouldLazilyUpdateOnCacheHit Whether the regional cache entry should be updated in
171198 * the background or not when it experiences a cache hit.
199+ * @param opts.defaultLongLivedTtlSec The default age to use for long-lived cache entries.
200+ * When no revalidate is provided, the default age will be used.
201+ * @default `THIRTY_MINUTES_IN_SECONDS`
172202 *
173203 * @default `false` for the `short-lived` mode, and `true` for the `long-lived` mode.
174204 */
175205export function withRegionalCache ( cache : IncrementalCache , opts : Options ) {
176206 return new RegionalCache ( cache , opts ) ;
177207}
208+
209+ /**
210+ * Extract the list of tags from a cache entry.
211+ */
212+ function getTagsFromCacheEntry ( entry : IncrementalCacheEntry < boolean > ) : string [ ] | undefined {
213+ if ( "tags" in entry . value && entry . value . tags ) {
214+ return entry . value . tags ;
215+ }
216+
217+ if (
218+ "meta" in entry . value &&
219+ entry . value . meta &&
220+ "headers" in entry . value . meta &&
221+ entry . value . meta . headers
222+ ) {
223+ const rawTags = entry . value . meta . headers [ "x-next-cache-tags" ] ;
224+ if ( typeof rawTags === "string" ) {
225+ return rawTags . split ( "," ) ;
226+ }
227+ }
228+ }
0 commit comments