@@ -71,35 +71,66 @@ describe('page router', () => {
71
71
'500.html' ,
72
72
] )
73
73
74
+ // blob mtime is unpredictable, so this is just waiting for all blobs used from builds to get stale
75
+ await new Promise < void > ( ( resolve ) => setTimeout ( resolve , 10_000 ) )
76
+
74
77
// test the function call
75
78
const call1 = await invokeFunction ( ctx , { url : 'static/revalidate-automatic' } )
76
79
const call1Date = load ( call1 . body ) ( '[data-testid="date-now"]' ) . text ( )
77
80
expect ( call1 . statusCode ) . toBe ( 200 )
78
81
expect ( load ( call1 . body ) ( 'h1' ) . text ( ) ) . toBe ( 'Show #71' )
79
- // Because we're using mtime instead of Date.now() first invocation will actually be a cache miss.
80
- expect ( call1 . headers , 'a cache miss on the first invocation of a prerendered page' ) . toEqual (
82
+ // We waited for blobs to get stale so first invocation will actually be a cache hit
83
+ // with stale being served, while fresh is generated in background
84
+ expect ( call1 . headers , 'a stale page served with swr' ) . toEqual (
81
85
expect . objectContaining ( {
82
- 'cache-status' : expect . stringMatching ( / " N e x t .j s " ; m i s s / ) ,
86
+ 'cache-status' : '"Next.js"; hit; fwd=stale' ,
87
+ 'netlify-cdn-cache-control' : 's-maxage=5, stale-while-revalidate=31536000' ,
88
+ } ) ,
89
+ )
90
+
91
+ // wait to have page regenerated in the background
92
+ await new Promise < void > ( ( resolve ) => setTimeout ( resolve , 500 ) )
93
+
94
+ const call2 = await invokeFunction ( ctx , { url : 'static/revalidate-automatic' } )
95
+ const call2Date = load ( call2 . body ) ( '[data-testid="date-now"]' ) . text ( )
96
+ expect ( call2 . statusCode ) . toBe ( 200 )
97
+ expect ( load ( call2 . body ) ( 'h1' ) . text ( ) ) . toBe ( 'Show #71' )
98
+ expect (
99
+ call2 . headers ,
100
+ 'fresh page that was generated in background during previous call was served' ,
101
+ ) . toEqual (
102
+ expect . objectContaining ( {
103
+ 'cache-status' : '"Next.js"; hit' ,
83
104
'netlify-cdn-cache-control' : 's-maxage=5, stale-while-revalidate=31536000' ,
84
105
} ) ,
85
106
)
107
+ expect (
108
+ call2Date . localeCompare ( call1Date ) ,
109
+ 'the date of regenerated page is newer than initial stale page' ,
110
+ ) . toBeGreaterThan ( 0 )
111
+
112
+ // ping that should serve the stale page for static/revalidate-slow, while revalidating in background
113
+ await invokeFunction ( ctx , { url : 'static/revalidate-slow' } )
86
114
87
115
// wait to have a stale page
88
- await new Promise < void > ( ( resolve ) => setTimeout ( resolve , 9_000 ) )
116
+ await new Promise < void > ( ( resolve ) => setTimeout ( resolve , 6_000 ) )
89
117
90
118
// Ping this now so we can wait in parallel
91
119
const callLater = await invokeFunction ( ctx , { url : 'static/revalidate-slow' } )
92
120
93
- // now it should be a cache miss
94
- const call2 = await invokeFunction ( ctx , { url : 'static/revalidate-automatic' } )
95
- const call2Date = load ( call2 . body ) ( '[data-testid="date-now"]' ) . text ( )
96
- expect ( call2 . statusCode ) . toBe ( 200 )
97
- expect ( call2 . headers , 'a cache miss on a stale page' ) . toEqual (
121
+ // over 5 seconds since it was regenerated, so we should get stale response,
122
+ // while fresh is generated in the background
123
+ const call3 = await invokeFunction ( ctx , { url : 'static/revalidate-automatic' } )
124
+ const call3Date = load ( call3 . body ) ( '[data-testid="date-now"]' ) . text ( )
125
+ expect ( call3 . statusCode ) . toBe ( 200 )
126
+ expect ( call3 . headers , 'a stale page served with swr' ) . toEqual (
98
127
expect . objectContaining ( {
99
- 'cache-status' : expect . stringMatching ( / " N e x t .j s " ; m i s s / ) ,
128
+ 'cache-status' : ' "Next.js"; hit; fwd=stale' ,
100
129
} ) ,
101
130
)
102
- expect ( call2Date , 'the date was cached and is matching the initial one' ) . not . toBe ( call1Date )
131
+ expect ( call3Date , 'the date was cached and is matching the initially regenerated one' ) . toBe (
132
+ call2Date ,
133
+ )
103
134
104
135
// Slow revalidate should still be a hit, but the maxage should be updated
105
136
const callLater2 = await invokeFunction ( ctx , { url : 'static/revalidate-slow' } )
@@ -108,20 +139,23 @@ describe('page router', () => {
108
139
109
140
expect ( callLater2 . headers , 'date header matches the cached value' ) . toEqual (
110
141
expect . objectContaining ( {
111
- 'cache-status' : expect . stringMatching ( / " N e x t .j s " ; h i t / ) ,
142
+ 'cache-status' : ' "Next.js"; hit' ,
112
143
date : callLater . headers [ 'date' ] ,
113
144
} ) ,
114
145
)
115
146
116
147
// it does not wait for the cache.set so we have to manually wait here until the blob storage got populated
117
- await new Promise < void > ( ( resolve ) => setTimeout ( resolve , 100 ) )
148
+ await new Promise < void > ( ( resolve ) => setTimeout ( resolve , 500 ) )
118
149
119
150
// now the page should be in cache again and we should get a cache hit
120
- const call3 = await invokeFunction ( ctx , { url : 'static/revalidate-automatic' } )
121
- const call3Date = load ( call3 . body ) ( '[data-testid="date-now"]' ) . text ( )
122
- expect ( call2Date , 'the date was not cached' ) . toBe ( call3Date )
123
- expect ( call3 . statusCode ) . toBe ( 200 )
124
- expect ( call3 . headers , 'a cache hit after dynamically regenerating the stale page' ) . toEqual (
151
+ const call4 = await invokeFunction ( ctx , { url : 'static/revalidate-automatic' } )
152
+ const call4Date = load ( call4 . body ) ( '[data-testid="date-now"]' ) . text ( )
153
+ expect ( call4Date , 'the date was not cached' ) . not . toBe ( call3Date )
154
+ expect ( call4 . statusCode ) . toBe ( 200 )
155
+ expect (
156
+ call4 . headers ,
157
+ 'a cache hit after dynamically regenerating the stale page in the background' ,
158
+ ) . toEqual (
125
159
expect . objectContaining ( {
126
160
'cache-status' : expect . stringMatching ( / " N e x t .j s " ; h i t / ) ,
127
161
} ) ,
@@ -148,15 +182,18 @@ describe('app router', () => {
148
182
'ad74683e49684ff4fe3d01ba1bef627bc0e38b61fa6bd8244145fbaca87f3c49' ,
149
183
] )
150
184
185
+ // blob mtime is unpredictable, so this is just waiting for all blobs used from builds to get stale
186
+ await new Promise < void > ( ( resolve ) => setTimeout ( resolve , 10_000 ) )
187
+
151
188
// test the function call
152
189
const post1 = await invokeFunction ( ctx , { url : 'posts/1' } )
153
190
const post1Date = load ( post1 . body ) ( '[data-testid="date-now"]' ) . text ( )
154
191
expect ( post1 . statusCode ) . toBe ( 200 )
155
192
expect ( load ( post1 . body ) ( 'h1' ) . text ( ) ) . toBe ( 'Revalidate Fetch' )
156
- expect ( post1 . headers , 'a cache miss on the first invocation of a prerendered page' ) . toEqual (
157
- // It will be stale/miss instead of hit
193
+ expect ( post1 . headers , 'a stale response on the first invocation of a prerendered page' ) . toEqual (
194
+ // It will be stale instead of hit
158
195
expect . objectContaining ( {
159
- 'cache-status' : expect . stringMatching ( / " N e x t .j s " ; m i s s / ) ,
196
+ 'cache-status' : ' "Next.js"; hit; fwd=stale' ,
160
197
'netlify-cdn-cache-control' : 's-maxage=5, stale-while-revalidate=31536000' ,
161
198
} ) ,
162
199
)
@@ -173,32 +210,33 @@ describe('app router', () => {
173
210
)
174
211
175
212
// wait to have a stale page
176
- await new Promise < void > ( ( resolve ) => setTimeout ( resolve , 5_000 ) )
213
+ await new Promise < void > ( ( resolve ) => setTimeout ( resolve , 6_000 ) )
177
214
// after the dynamic call of `posts/3` it should be in cache, not this is after the timout as the cache set happens async
178
215
expect ( await ctx . blobStore . get ( encodeBlobKey ( '/posts/3' ) ) ) . not . toBeNull ( )
179
216
180
217
const stale = await invokeFunction ( ctx , { url : 'posts/1' } )
181
218
const staleDate = load ( stale . body ) ( '[data-testid="date-now"]' ) . text ( )
182
219
expect ( stale . statusCode ) . toBe ( 200 )
183
- expect ( stale . headers , 'a cache miss on a stale page ' ) . toEqual (
220
+ expect ( stale . headers , 'a stale swr page is served ' ) . toEqual (
184
221
expect . objectContaining ( {
185
- 'cache-status' : expect . stringMatching ( / " N e x t .j s " ; m i s s / ) ,
222
+ 'cache-status' : ' "Next.js"; hit; fwd=stale' ,
186
223
} ) ,
187
224
)
188
- // it should have a new date rendered
225
+ // it should've been regenerated in the background after the first call
226
+ // so the date should be different
189
227
expect ( staleDate , 'the date was cached and is matching the initial one' ) . not . toBe ( post1Date )
190
228
191
229
// it does not wait for the cache.set so we have to manually wait here until the blob storage got populated
192
- await new Promise < void > ( ( resolve ) => setTimeout ( resolve , 100 ) )
230
+ await new Promise < void > ( ( resolve ) => setTimeout ( resolve , 500 ) )
193
231
194
232
// now the page should be in cache again and we should get a cache hit
195
233
const cached = await invokeFunction ( ctx , { url : 'posts/1' } )
196
234
const cachedDate = load ( cached . body ) ( '[data-testid="date-now"]' ) . text ( )
197
235
expect ( cached . statusCode ) . toBe ( 200 )
198
- expect ( staleDate , 'the date was not cached ' ) . toBe ( cachedDate )
236
+ expect ( cachedDate , 'the date is not stale ' ) . not . toBe ( staleDate )
199
237
expect ( cached . headers , 'a cache hit after dynamically regenerating the stale page' ) . toEqual (
200
238
expect . objectContaining ( {
201
- 'cache-status' : expect . stringMatching ( / " N e x t .j s " ; h i t / ) ,
239
+ 'cache-status' : ' "Next.js"; hit' ,
202
240
} ) ,
203
241
)
204
242
} )
@@ -240,7 +278,10 @@ describe('route', () => {
240
278
} )
241
279
expect ( blobEntry ) . not . toBeNull ( )
242
280
243
- // test the first invocation of the route
281
+ // blob mtime is unpredictable, so this is just waiting for all blobs used from builds to get stale
282
+ await new Promise < void > ( ( resolve ) => setTimeout ( resolve , 10_000 ) )
283
+
284
+ // test the first invocation of the route - we should get stale response while fresh is generated in the background
244
285
const call1 = await invokeFunction ( ctx , { url : '/api/revalidate-handler' } )
245
286
const call1Body = JSON . parse ( call1 . body )
246
287
const call1Time = call1Body . time
@@ -251,40 +292,63 @@ describe('route', () => {
251
292
name : 'Under the Dome' ,
252
293
} ) ,
253
294
} )
254
- expect ( call1 . headers , 'a cache miss on the first invocation ' ) . toEqual (
295
+ expect ( call1 . headers , 'a stale route served with swr ' ) . toEqual (
255
296
expect . objectContaining ( {
256
- 'cache-status' : expect . stringMatching ( / " N e x t .j s " ; m i s s / ) ,
297
+ 'cache-status' : ' "Next.js"; hit; fwd=stale' ,
257
298
} ) ,
258
299
)
259
- // wait to have a stale route
260
- await new Promise < void > ( ( resolve ) => setTimeout ( resolve , 7_000 ) )
300
+
301
+ // it does not wait for the cache.set so we have to manually wait here until the blob storage got populated
302
+ await new Promise < void > ( ( resolve ) => setTimeout ( resolve , 500 ) )
261
303
262
304
const call2 = await invokeFunction ( ctx , { url : '/api/revalidate-handler' } )
263
305
const call2Body = JSON . parse ( call2 . body )
264
306
const call2Time = call2Body . time
265
307
expect ( call2 . statusCode ) . toBe ( 200 )
266
- // it should have a new date rendered
267
- expect ( call2Body ) . toMatchObject ( { data : expect . objectContaining ( { id : 1 } ) } )
268
- expect ( call2 . headers , 'a cache miss on a stale route' ) . toEqual (
308
+ expect ( call2Body ) . toMatchObject ( {
309
+ data : expect . objectContaining ( {
310
+ id : 1 ,
311
+ name : 'Under the Dome' ,
312
+ } ) ,
313
+ } )
314
+ expect ( call2 . headers , 'a cache hit on the second invocation' ) . toEqual (
269
315
expect . objectContaining ( {
270
- 'cache-status' : expect . stringMatching ( / " N e x t .j s " ; m i s s / ) ,
316
+ 'cache-status' : ' "Next.js"; hit' ,
271
317
} ) ,
272
318
)
273
- expect ( call1Time , 'the date is a new one on a stale route ' ) . not . toBe ( call2Time )
319
+ expect ( call2Time , 'the date is new' ) . not . toBe ( call1Time )
274
320
275
- // it does not wait for the cache.set so we have to manually wait here until the blob storage got populated
276
- await new Promise < void > ( ( resolve ) => setTimeout ( resolve , 100 ) )
321
+ // wait to have a stale route again
322
+ await new Promise < void > ( ( resolve ) => setTimeout ( resolve , 8_000 ) )
277
323
278
324
const call3 = await invokeFunction ( ctx , { url : '/api/revalidate-handler' } )
279
- expect ( call3 . statusCode ) . toBe ( 200 )
280
325
const call3Body = JSON . parse ( call3 . body )
281
326
const call3Time = call3Body . time
327
+ expect ( call3 . statusCode ) . toBe ( 200 )
282
328
expect ( call3Body ) . toMatchObject ( { data : expect . objectContaining ( { id : 1 } ) } )
283
- expect ( call3 . headers , 'a cache hit after dynamically regenerating the stale route ' ) . toEqual (
329
+ expect ( call3 . headers , 'a stale route served with swr ' ) . toEqual (
284
330
expect . objectContaining ( {
285
- 'cache-status' : expect . stringMatching ( / " N e x t .j s " ; h i t / ) ,
331
+ 'cache-status' : '"Next.js"; hit; fwd=stale' ,
332
+ } ) ,
333
+ )
334
+ expect (
335
+ call2Time ,
336
+ 'the date is the old one on the stale route, while the refresh is happening in the background' ,
337
+ ) . toBe ( call3Time )
338
+
339
+ // it does not wait for the cache.set so we have to manually wait here until the blob storage got populated
340
+ await new Promise < void > ( ( resolve ) => setTimeout ( resolve , 500 ) )
341
+
342
+ const call4 = await invokeFunction ( ctx , { url : '/api/revalidate-handler' } )
343
+ expect ( call4 . statusCode ) . toBe ( 200 )
344
+ const call4Body = JSON . parse ( call4 . body )
345
+ const call4Time = call4Body . time
346
+ expect ( call4Body ) . toMatchObject ( { data : expect . objectContaining ( { id : 1 } ) } )
347
+ expect ( call4 . headers , 'a cache hit after dynamically regenerating the stale route' ) . toEqual (
348
+ expect . objectContaining ( {
349
+ 'cache-status' : '"Next.js"; hit' ,
286
350
} ) ,
287
351
)
288
- expect ( call3Time , 'the date was cached as well ' ) . toBe ( call2Time )
352
+ expect ( call4Time , 'the date is new ' ) . not . toBe ( call3Time )
289
353
} )
290
354
} )
0 commit comments