Skip to content

Commit

Permalink
cache: "default" shouldn't get cached in route handlers (#70937)
Browse files Browse the repository at this point in the history
In Next 15, route handlers should no longer cache any `fetch` requests
by default. We do this by bailing out of caching if no explicit cache
config is defined. However, this misses the case where `cache:
"default"` is specified. Since the default caching behavior is to not
cache, this should also opt into the automatic no cache behavior.

This adds a test for the route handler and still validates that it
doesn't impact page-level ISR cache.
  • Loading branch information
ztanner authored Oct 8, 2024
1 parent 99e24f5 commit 710b0ae
Show file tree
Hide file tree
Showing 4 changed files with 87 additions and 1 deletion.
6 changes: 5 additions & 1 deletion packages/next/src/server/lib/patch-fetch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -409,6 +409,7 @@ export function createPatchedFetcher(
* - Fetch cache configs are not set. Specifically:
* - A page fetch cache mode is not set (export const fetchCache=...)
* - A fetch cache mode is not set in the fetch call (fetch(url, { cache: ... }))
* or the fetch cache mode is set to 'default'
* - A fetch revalidate value is not set in the fetch call (fetch(url, { revalidate: ... }))
* - OR the fetch comes after a configuration that triggered dynamic rendering (e.g., reading cookies())
* and the fetch was considered uncacheable (e.g., POST method or has authorization headers)
Expand All @@ -417,7 +418,10 @@ export function createPatchedFetcher(
// eslint-disable-next-line eqeqeq
pageFetchCacheMode == undefined &&
// eslint-disable-next-line eqeqeq
currentFetchCacheConfig == undefined &&
(currentFetchCacheConfig == undefined ||
// when considering whether to opt into the default "no-cache" fetch semantics,
// a "default" cache config should be treated the same as no cache config
currentFetchCacheConfig === 'default') &&
// eslint-disable-next-line eqeqeq
currentFetchRevalidate == undefined
const autoNoCache =
Expand Down
59 changes: 59 additions & 0 deletions test/e2e/app-dir/app-static/app-static.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,44 @@ describe('app-dir static/dynamic handling', () => {
}
})

it('should correctly handle "default" cache statuses', async () => {
const res = await next.fetch('/default-config-fetch')
expect(res.status).toBe(200)

const html = await res.text()
const $ = cheerio.load(html)
const data = $('#data').text()

expect(data).toBeTruthy()

const res2 = await next.fetch('/default-config-fetch')
const html2 = await res2.text()
const data2 = cheerio.load(html2)('#data').text()

if (isNextDev) {
expect(data).not.toBe(data2)
} else {
// "default" cache does not impact ISR handling on a page, similar to the above test
// case for no fetch config
const pageCache = (
res.headers.get('x-vercel-cache') || res.headers.get('x-nextjs-cache')
).toLowerCase()

expect(pageCache).toBeTruthy()
expect(pageCache).not.toBe('MISS')
expect(data).toBe(data2)
}

// route handlers should not automatically cache fetches with "default" cache
const routeRes = await next.fetch('/default-config-fetch/api')
const initialRouteData = (await routeRes.json()).data

const nextRes = await next.fetch('/default-config-fetch/api')
const newRouteData = (await nextRes.json()).data

expect(initialRouteData).not.toEqual(newRouteData)
})

it('should still cache even though the `traceparent` header was different', async () => {
const res = await next.fetch('/strip-header-traceparent')
expect(res.status).toBe(200)
Expand Down Expand Up @@ -715,6 +753,9 @@ describe('app-dir static/dynamic handling', () => {
[
"(new)/custom/page.js",
"(new)/custom/page_client-reference-manifest.js",
"(new)/default-config-fetch/api/route.js",
"(new)/default-config-fetch/page.js",
"(new)/default-config-fetch/page_client-reference-manifest.js",
"(new)/no-config-fetch/page.js",
"(new)/no-config-fetch/page_client-reference-manifest.js",
"_not-found.html",
Expand Down Expand Up @@ -753,6 +794,8 @@ describe('app-dir static/dynamic handling', () => {
"default-cache-search-params/page_client-reference-manifest.js",
"default-cache/page.js",
"default-cache/page_client-reference-manifest.js",
"default-config-fetch.html",
"default-config-fetch.rsc",
"dynamic-error/[id]/page.js",
"dynamic-error/[id]/page_client-reference-manifest.js",
"dynamic-no-gen-params-ssr/[slug]/page.js",
Expand Down Expand Up @@ -1186,6 +1229,22 @@ describe('app-dir static/dynamic handling', () => {
"initialRevalidateSeconds": false,
"srcRoute": "/blog/[author]/[slug]",
},
"/default-config-fetch": {
"dataRoute": "/default-config-fetch.rsc",
"experimentalBypassFor": [
{
"key": "Next-Action",
"type": "header",
},
{
"key": "content-type",
"type": "header",
"value": "multipart/form-data;.*",
},
],
"initialRevalidateSeconds": false,
"srcRoute": "/default-config-fetch",
},
"/force-cache": {
"dataRoute": "/force-cache.rsc",
"experimentalBypassFor": [
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { NextResponse } from 'next/server'

export async function GET() {
const data = await fetch(
'https://next-data-api-endpoint.vercel.app/api/random?default-config-fetch',
{ cache: 'default' }
).then((res) => res.text())

return NextResponse.json({ data })
}
13 changes: 13 additions & 0 deletions test/e2e/app-dir/app-static/app/(new)/default-config-fetch/page.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
export default async function Page() {
const data = await fetch(
`https://next-data-api-endpoint.vercel.app/api/random`,
{ cache: 'default' }
).then((res) => res.text())

return (
<>
<p>/default-config-fetch</p>
<p id="data">{data}</p>
</>
)
}

0 comments on commit 710b0ae

Please sign in to comment.