Skip to content

Commit f602b29

Browse files
authored
default fetchCache to no-store when force-dynamic is set (#64145)
`fetchCache` is a more fine-grained segment level cache-control configuration that most people shouldn't have to use. Current semantics of `dynamic = "force-dynamic"` will still treat the fetch as cacheable unless explicitly overriding it in the fetch, or setting a segment level `fetchCache`. The `dynamic` cache configuration should be seen as a "top-level" configuration, while more fine-grained controls should inherit logical defaults from the top-level. Otherwise this forces people to opt-into the `fetchCache` configuration, or manually override each `fetch` call, which isn't what you'd expect when forcing a segment to be dynamic. This will default to not attempting to cache the fetch when `force-dynamic` is used. As a result, I had to update one of the `app-static` tests to use `revalidate: 0` rather than `force-dynamic`, as the revalidate behavior is slightly different in that it won't modify the revalidation time on a fetch if it's non-zero. Closes NEXT-2067
1 parent 14ed612 commit f602b29

File tree

7 files changed

+75
-4
lines changed

7 files changed

+75
-4
lines changed

docs/02-app/02-api-reference/02-file-conventions/route-segment-config.mdx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,11 @@ export const dynamic = 'auto'
6262
> **Good to know**: The new model in the `app` directory favors granular caching control at the `fetch` request level over the binary all-or-nothing model of `getServerSideProps` and `getStaticProps` at the page-level in the `pages` directory. The `dynamic` option is a way to opt back in to the previous model as a convenience and provides a simpler migration path.
6363
6464
- **`'auto'`** (default): The default option to cache as much as possible without preventing any components from opting into dynamic behavior.
65-
- **`'force-dynamic'`**: Force [dynamic rendering](/docs/app/building-your-application/rendering/server-components#dynamic-rendering), which will result in routes being rendered for each user at request time. This option is equivalent to `getServerSideProps()` in the `pages` directory.
65+
- **`'force-dynamic'`**: Force [dynamic rendering](/docs/app/building-your-application/rendering/server-components#dynamic-rendering), which will result in routes being rendered for each user at request time. This option is equivalent to:
66+
67+
- `getServerSideProps()` in the `pages` directory.
68+
- Setting the option of every `fetch()` request in a layout or page to `{ cache: 'no-store', next: { revalidate: 0 } }`.
69+
- Setting the segment config to `export const fetchCache = 'force-no-store'`
6670

6771
- **`'error'`**: Force static rendering and cache the data of a layout or page by causing an error if any components use [dynamic functions](/docs/app/building-your-application/rendering/server-components#dynamic-functions) or uncached data. This option is equivalent to:
6872
- `getStaticProps()` in the `pages` directory.

packages/next/src/server/lib/patch-fetch.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -310,7 +310,13 @@ function createPatchedFetcher(
310310
_cache === 'no-cache' ||
311311
_cache === 'no-store' ||
312312
fetchCacheMode === 'force-no-store' ||
313-
fetchCacheMode === 'only-no-store'
313+
fetchCacheMode === 'only-no-store' ||
314+
// If no explicit fetch cache mode is set, but dynamic = `force-dynamic` is set,
315+
// we shouldn't consider caching the fetch. This is because the `dynamic` cache
316+
// is considered a "top-level" cache mode, whereas something like `fetchCache` is more
317+
// fine-grained. Top-level modes are responsible for setting reasonable defaults for the
318+
// other configurations.
319+
(!fetchCacheMode && staticGenerationStore.forceDynamic)
314320
) {
315321
curRevalidate = 0
316322
}

test/e2e/app-dir/app-static/app-static.test.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,34 @@ createNextDescribe(
104104
)
105105
})
106106

107+
it('should infer a fetchCache of force-no-store when force-dynamic is used', async () => {
108+
const $ = await next.render$(
109+
'/force-dynamic-fetch-cache/no-fetch-cache'
110+
)
111+
const initData = $('#data').text()
112+
await retry(async () => {
113+
const $2 = await next.render$(
114+
'/force-dynamic-fetch-cache/no-fetch-cache'
115+
)
116+
expect($2('#data').text()).toBeTruthy()
117+
expect($2('#data').text()).not.toBe(initData)
118+
})
119+
})
120+
121+
it('fetchCache config should supercede dynamic config when force-dynamic is used', async () => {
122+
const $ = await next.render$(
123+
'/force-dynamic-fetch-cache/with-fetch-cache'
124+
)
125+
const initData = $('#data').text()
126+
await retry(async () => {
127+
const $2 = await next.render$(
128+
'/force-dynamic-fetch-cache/with-fetch-cache'
129+
)
130+
expect($2('#data').text()).toBeTruthy()
131+
expect($2('#data').text()).toBe(initData)
132+
})
133+
})
134+
107135
if (!process.env.CUSTOM_CACHE_HANDLER) {
108136
it('should honor force-static with fetch cache: no-store correctly', async () => {
109137
const res = await next.fetch('/force-static-fetch-no-store')
@@ -667,6 +695,10 @@ createNextDescribe(
667695
"force-cache/page_client-reference-manifest.js",
668696
"force-dynamic-catch-all/[slug]/[[...id]]/page.js",
669697
"force-dynamic-catch-all/[slug]/[[...id]]/page_client-reference-manifest.js",
698+
"force-dynamic-fetch-cache/no-fetch-cache/page.js",
699+
"force-dynamic-fetch-cache/no-fetch-cache/page_client-reference-manifest.js",
700+
"force-dynamic-fetch-cache/with-fetch-cache/page.js",
701+
"force-dynamic-fetch-cache/with-fetch-cache/page_client-reference-manifest.js",
670702
"force-dynamic-no-prerender/[id]/page.js",
671703
"force-dynamic-no-prerender/[id]/page_client-reference-manifest.js",
672704
"force-dynamic-prerender/[slug]/page.js",
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
export const dynamic = 'force-dynamic'
2+
3+
export default async function Page() {
4+
const data = await fetch(
5+
'https://next-data-api-endpoint.vercel.app/api/random'
6+
).then((res) => res.text())
7+
8+
return (
9+
<>
10+
<p id="page">/force-dynamic-fetch-cache/no-fetch-cache</p>
11+
<p id="data">{data}</p>
12+
</>
13+
)
14+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
export const dynamic = 'force-dynamic'
2+
export const fetchCache = 'force-cache'
3+
4+
export default async function Page() {
5+
const data = await fetch(
6+
'https://next-data-api-endpoint.vercel.app/api/random'
7+
).then((res) => res.text())
8+
9+
return (
10+
<>
11+
<p id="page">/force-dynamic-fetch-cache/with-fetch-cache</p>
12+
<p id="data">{data}</p>
13+
</>
14+
)
15+
}

test/e2e/app-dir/app-static/app/stale-cache-serving/app-page/page.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
export const dynamic = 'force-dynamic'
1+
export const revalidate = 0
22

33
const delay = 3000
44

test/e2e/app-dir/app-static/app/stale-cache-serving/route-handler/route.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { NextRequest, NextResponse } from 'next/server'
22

3-
export const dynamic = 'force-dynamic'
3+
export const revalidate = 0
44

55
const delay = 3000
66

0 commit comments

Comments
 (0)