diff --git a/packages/next/src/server/lib/patch-fetch.ts b/packages/next/src/server/lib/patch-fetch.ts index 7cd0dce23088e..80e8d8ebaaa90 100644 --- a/packages/next/src/server/lib/patch-fetch.ts +++ b/packages/next/src/server/lib/patch-fetch.ts @@ -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) @@ -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 = diff --git a/test/e2e/app-dir/app-static/app-static.test.ts b/test/e2e/app-dir/app-static/app-static.test.ts index a58e870c576ef..fe6444a7c6a86 100644 --- a/test/e2e/app-dir/app-static/app-static.test.ts +++ b/test/e2e/app-dir/app-static/app-static.test.ts @@ -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) @@ -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", @@ -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", @@ -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": [ diff --git a/test/e2e/app-dir/app-static/app/(new)/default-config-fetch/api/route.js b/test/e2e/app-dir/app-static/app/(new)/default-config-fetch/api/route.js new file mode 100644 index 0000000000000..63482df1faf77 --- /dev/null +++ b/test/e2e/app-dir/app-static/app/(new)/default-config-fetch/api/route.js @@ -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 }) +} diff --git a/test/e2e/app-dir/app-static/app/(new)/default-config-fetch/page.js b/test/e2e/app-dir/app-static/app/(new)/default-config-fetch/page.js new file mode 100644 index 0000000000000..ef001cd10a7ca --- /dev/null +++ b/test/e2e/app-dir/app-static/app/(new)/default-config-fetch/page.js @@ -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 ( + <> +

/default-config-fetch

+

{data}

+ + ) +}