Skip to content

Commit eb756a6

Browse files
authored
[Cache Components] give the "seconds" profile a 30s staleTime (#82332)
Closes NAR-262
1 parent 2dc4542 commit eb756a6

File tree

7 files changed

+132
-27
lines changed

7 files changed

+132
-27
lines changed

packages/next/src/server/config-shared.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1355,7 +1355,7 @@ export const defaultConfig = Object.freeze({
13551355
expire: INFINITE_CACHE,
13561356
},
13571357
seconds: {
1358-
stale: undefined, // defaults to staleTimes.dynamic
1358+
stale: 30, // 30 seconds
13591359
revalidate: 1, // 1 second
13601360
expire: 60, // 1 minute
13611361
},

packages/next/src/server/config.ts

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -942,18 +942,6 @@ function assignDefaults(
942942
result.expireTime ?? defaultDefault.expire
943943
}
944944
}
945-
// This is the most dynamic cache life profile.
946-
const secondsCacheLifeProfile = result.experimental.cacheLife['seconds']
947-
if (
948-
secondsCacheLifeProfile &&
949-
secondsCacheLifeProfile.stale === undefined
950-
) {
951-
// We default this to whatever stale time you had configured for dynamic content.
952-
// Since this is basically a dynamic cache life profile.
953-
const dynamicStaleTime = result.experimental.staleTimes?.dynamic
954-
secondsCacheLifeProfile.stale =
955-
dynamicStaleTime ?? defaultConfig.experimental?.staleTimes?.dynamic
956-
}
957945
}
958946

959947
if (result.experimental?.cacheHandlers) {
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import { Suspense } from 'react'
2+
import { cachedDelay, DebugRenderKind } from '../../../shared'
3+
import { unstable_cacheLife } from 'next/cache'
4+
5+
export default async function Page() {
6+
return (
7+
<main>
8+
<DebugRenderKind />
9+
<p id="intro">
10+
This page uses a short-lived private cache (with cacheLife("seconds")),
11+
which should not be included in a static prefetch, but should be
12+
included in a runtime prefetch, because it has a long enough stale time
13+
(&ge; RUNTIME_PREFETCH_DYNAMIC_STALE, 30s)
14+
</p>
15+
<Suspense fallback={<div style={{ color: 'grey' }}>Loading...</div>}>
16+
<ShortLivedCache />
17+
</Suspense>
18+
</main>
19+
)
20+
}
21+
22+
async function ShortLivedCache() {
23+
'use cache: private'
24+
unstable_cacheLife('seconds')
25+
await cachedDelay([__filename])
26+
27+
return (
28+
<div style={{ border: '1px solid blue', padding: '1em' }}>
29+
Short-lived cached content
30+
<div id="cached-value">{Date.now()}</div>
31+
</div>
32+
)
33+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import { Suspense } from 'react'
2+
import { cachedDelay, DebugRenderKind } from '../../../shared'
3+
import { unstable_cacheLife } from 'next/cache'
4+
5+
export default async function Page() {
6+
return (
7+
<main>
8+
<DebugRenderKind />
9+
<p id="intro">
10+
This page uses a short-lived public cache (with cacheLife("seconds")),
11+
which should not be included in a static prefetch, but should be
12+
included in a runtime prefetch, because it has a long enough stale time
13+
(&ge; RUNTIME_PREFETCH_DYNAMIC_STALE, 30s)
14+
</p>
15+
<Suspense fallback={<div style={{ color: 'grey' }}>Loading...</div>}>
16+
<ShortLivedCache />
17+
</Suspense>
18+
</main>
19+
)
20+
}
21+
22+
async function ShortLivedCache() {
23+
'use cache'
24+
unstable_cacheLife('seconds')
25+
await cachedDelay([__filename])
26+
27+
return (
28+
<div style={{ border: '1px solid blue', padding: '1em' }}>
29+
Short-lived cached content
30+
<div id="cached-value">{Date.now()}</div>
31+
</div>
32+
)
33+
}

test/e2e/app-dir/segment-cache/prefetch-runtime/app/(default)/caches/public-short-expire-long-stale/page.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ export default async function Page() {
1010
This page uses a short-lived public cache (expire &lt; DYNAMIC_EXPIRE,
1111
5min), which should not be included in a static prefetch, but should be
1212
included in a runtime prefetch, because it has a long enough stale time
13-
(&gt; RUNTIME_PREFETCH_DYNAMIC_STALE, 30s)
13+
(&ge; RUNTIME_PREFETCH_DYNAMIC_STALE, 30s)
1414
</p>
1515
<Suspense fallback={<div style={{ color: 'grey' }}>Loading...</div>}>
1616
<ShortLivedCache />

test/e2e/app-dir/segment-cache/prefetch-runtime/app/(default)/page.tsx

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,40 @@ export default async function Page() {
189189
</li>
190190
</ul>
191191
</li>
192+
<li>
193+
public, cacheLife("seconds")
194+
<ul>
195+
<li>
196+
<DebugLinkAccordion
197+
href="/caches/public-seconds"
198+
prefetch={'auto'}
199+
/>
200+
</li>
201+
<li>
202+
<DebugLinkAccordion
203+
href="/caches/public-seconds"
204+
prefetch={true}
205+
/>
206+
</li>
207+
</ul>
208+
</li>
209+
<li>
210+
private, cacheLife("seconds")
211+
<ul>
212+
<li>
213+
<DebugLinkAccordion
214+
href="/caches/private-seconds"
215+
prefetch={'auto'}
216+
/>
217+
</li>
218+
<li>
219+
<DebugLinkAccordion
220+
href="/caches/private-seconds"
221+
prefetch={true}
222+
/>
223+
</li>
224+
</ul>
225+
</li>
192226
</ul>
193227

194228
<h2>misc</h2>

test/e2e/app-dir/segment-cache/prefetch-runtime/prefetch-runtime.test.ts

Lines changed: 30 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -670,10 +670,32 @@ describe('<Link prefetch={true}> (runtime prefetch)', () => {
670670
})
671671

672672
describe('cache stale time handling', () => {
673-
it('includes short-lived public caches with a long enough staleTime', async () => {
674-
// If a cache has an expiration time under 5min (DYNAMIC_EXPIRE), we omit it from static prerenders.
675-
// However, it should still be included in a runtime prefetch if it's stale time is above 30s. (RUNTIME_PREFETCH_DYNAMIC_STALE)
676-
673+
it.each([
674+
{
675+
// If a cache has an expiration time under 5min (DYNAMIC_EXPIRE), we omit it from static prerenders.
676+
// However, it should still be included in a runtime prefetch if its stale time is >=30s. (RUNTIME_PREFETCH_DYNAMIC_STALE)
677+
description:
678+
'includes short-lived public caches with a long enough staleTime',
679+
staticContent: 'This page uses a short-lived public cache',
680+
path: '/caches/public-short-expire-long-stale',
681+
},
682+
{
683+
// If a cache has an expiration time under 5min (DYNAMIC_EXPIRE), we omit it from static prerenders.
684+
// However, it should still be included in a runtime prefetch if its stale time is >=30s. (RUNTIME_PREFETCH_DYNAMIC_STALE)
685+
// `cacheLife("seconds")` is deliberately set to have a stale time of 30s to stay above this treshold.
686+
description: 'includes public caches with cacheLife("seconds")',
687+
staticContent: 'This page uses a short-lived public cache',
688+
path: '/caches/public-seconds',
689+
},
690+
{
691+
// A Private cache will always be omitted from static prerenders.
692+
// However, it should still be included in a runtime prefetch if its stale time is >=30s. (RUNTIME_PREFETCH_DYNAMIC_STALE)
693+
// `cacheLife("seconds")` is deliberately set to have a stale time of 30s to stay above this treshold.
694+
description: 'includes private caches with cacheLife("seconds")',
695+
staticContent: 'This page uses a short-lived private cache',
696+
path: '/caches/private-seconds',
697+
},
698+
])('$description', async ({ path, staticContent }) => {
677699
let page: Playwright.Page
678700
const browser = await next.browser('/', {
679701
beforePageLoad(p: Playwright.Page) {
@@ -682,22 +704,20 @@ describe('<Link prefetch={true}> (runtime prefetch)', () => {
682704
})
683705
const act = createRouterAct(page)
684706

685-
const STATIC_CONTENT = 'This page uses a short-lived public cache'
686707
const DYNAMICALLY_PREFETCHABLE_CONTENT = 'Short-lived cached content'
687708

688709
// Reveal the link to trigger a static prefetch
689710
await act(async () => {
690711
const linkToggle = await browser.elementByCss(
691-
`input[data-prefetch="auto"][data-link-accordion="/caches/public-short-expire-long-stale"]`
712+
`input[data-prefetch="auto"][data-link-accordion="${path}"]`
692713
)
693714
await linkToggle.click()
694715
}, [
695716
// Should include the static shell
696717
{
697-
includes: STATIC_CONTENT,
718+
includes: staticContent,
698719
},
699720
// Should not include the short-lived cache
700-
// (We set the `expire` value to be under 5min, so it will be excluded from prerenders)
701721
{
702722
includes: DYNAMICALLY_PREFETCHABLE_CONTENT,
703723
block: 'reject',
@@ -707,12 +727,11 @@ describe('<Link prefetch={true}> (runtime prefetch)', () => {
707727
// Reveal the link to trigger a runtime prefetch
708728
await act(async () => {
709729
const linkToggle = await browser.elementByCss(
710-
`input[data-prefetch="runtime"][data-link-accordion="/caches/public-short-expire-long-stale"]`
730+
`input[data-prefetch="runtime"][data-link-accordion="${path}"]`
711731
)
712732
await linkToggle.click()
713733
}, [
714734
// Should include the short-lived cache
715-
// (We set `stale` to be above 30s, which means it shouldn't be omitted)
716735
{
717736
includes: DYNAMICALLY_PREFETCHABLE_CONTENT,
718737
},
@@ -721,9 +740,7 @@ describe('<Link prefetch={true}> (runtime prefetch)', () => {
721740
// Navigate to the page. We didn't include any uncached IO, so the page is fully prefetched,
722741
// and this shouldn't issue any more requests
723742
await act(async () => {
724-
await browser
725-
.elementByCss(`a[href="/caches/public-short-expire-long-stale"]`)
726-
.click()
743+
await browser.elementByCss(`a[href="${path}"]`).click()
727744
}, 'no-requests')
728745

729746
expect(await browser.elementByCss('main').text()).toInclude(

0 commit comments

Comments
 (0)