diff --git a/packages/next/src/export/routes/app-route.ts b/packages/next/src/export/routes/app-route.ts index bbb2eeba645bb..562e55d6803c0 100644 --- a/packages/next/src/export/routes/app-route.ts +++ b/packages/next/src/export/routes/app-route.ts @@ -91,7 +91,10 @@ export async function exportAppRoute( } const blob = await response.blob() - const revalidate = context.renderOpts.store?.revalidate || false + const revalidate = + typeof context.renderOpts.store?.revalidate === 'undefined' + ? false + : context.renderOpts.store.revalidate const headers = toNodeOutgoingHttpHeaders(response.headers) const cacheTags = (context.renderOpts as any).fetchTags 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 5d849d6064f9f..9dd85229a9496 100644 --- a/test/e2e/app-dir/app-static/app-static.test.ts +++ b/test/e2e/app-dir/app-static/app-static.test.ts @@ -210,6 +210,33 @@ createNextDescribe( } }) + if (!isDev && !process.env.CUSTOM_CACHE_HANDLER) { + it('should properly revalidate a route handler that triggers dynamic usage with force-static', async () => { + // wait for the revalidation period + let res = await next.fetch('/route-handler/no-store-force-static') + + let data = await res.json() + // grab the initial timestamp + const initialTimestamp = data.now + + // confirm its cached still + res = await next.fetch('/route-handler/no-store-force-static') + + data = await res.json() + + expect(data.now).toBe(initialTimestamp) + + // wait for the revalidation time + await waitFor(3000) + + // verify fresh data + res = await next.fetch('/route-handler/no-store-force-static') + data = await res.json() + + expect(data.now).not.toBe(initialTimestamp) + }) + } + if (!process.env.CUSTOM_CACHE_HANDLER) { it.each([ { @@ -639,6 +666,8 @@ createNextDescribe( "response-url/page.js", "response-url/page_client-reference-manifest.js", "route-handler-edge/revalidate-360/route.js", + "route-handler/no-store-force-static/route.js", + "route-handler/no-store/route.js", "route-handler/post/route.js", "route-handler/revalidate-360-isr/route.js", "route-handler/revalidate-360/route.js", @@ -1275,6 +1304,26 @@ createNextDescribe( "initialRevalidateSeconds": false, "srcRoute": "/partial-gen-params-no-additional-slug/[lang]/[slug]", }, + "/route-handler/no-store-force-static": { + "dataRoute": null, + "experimentalBypassFor": [ + { + "key": "Next-Action", + "type": "header", + }, + { + "key": "content-type", + "type": "header", + "value": "multipart/form-data", + }, + ], + "initialHeaders": { + "content-type": "application/json", + "x-next-cache-tags": "_N_T_/layout,_N_T_/route-handler/layout,_N_T_/route-handler/no-store-force-static/layout,_N_T_/route-handler/no-store-force-static/route,_N_T_/route-handler/no-store-force-static", + }, + "initialRevalidateSeconds": 3, + "srcRoute": "/route-handler/no-store-force-static", + }, "/route-handler/revalidate-360-isr": { "dataRoute": null, "experimentalBypassFor": [ diff --git a/test/e2e/app-dir/app-static/app/route-handler/no-store-force-static/route.ts b/test/e2e/app-dir/app-static/app/route-handler/no-store-force-static/route.ts new file mode 100644 index 0000000000000..d9abdedbe55e6 --- /dev/null +++ b/test/e2e/app-dir/app-static/app/route-handler/no-store-force-static/route.ts @@ -0,0 +1,11 @@ +export const dynamic = 'force-static' +export const revalidate = 3 + +export async function GET() { + const data = await fetch( + 'https://next-data-api-endpoint.vercel.app/api/random', + { cache: 'no-store' } + ).then((res) => res.text()) + + return Response.json({ now: Date.now(), data }) +} diff --git a/test/e2e/app-dir/app-static/app/route-handler/no-store/route.ts b/test/e2e/app-dir/app-static/app/route-handler/no-store/route.ts new file mode 100644 index 0000000000000..259c671c74bf7 --- /dev/null +++ b/test/e2e/app-dir/app-static/app/route-handler/no-store/route.ts @@ -0,0 +1,10 @@ +export const revalidate = 5 + +export async function GET() { + const data = await fetch( + 'https://next-data-api-endpoint.vercel.app/api/random', + { cache: 'no-store' } + ).then((res) => res.text()) + + return Response.json({ now: Date.now(), data }) +}