Skip to content

Commit

Permalink
fix revalidation issue with route handlers (#63213)
Browse files Browse the repository at this point in the history
### What
When a route handler uses an API that opts it into dynamic rendering
(such as `no-store` on a fetch), and also specifies a `revalidate` time,
the `revalidate` time is ignored and route is treated as fully static.

### Why
`revalidate: 0` and `revalidate: false` have different semantic
meanings: `false` essentially means cache forever, whereas `0` means
it's dynamic. Since `0` is also falsey, the code we have to fallback
with a default `revalidate` value for route handlers is incorrectly not
marking the route as dynamic, and as a result, caching the route without
an expiration time.

### How
This updates the fallback handling for app routes respect a revalidation
value of `0`, so that the page can properly be marked dynamic.

### Test Explanation
This adds 2 new routes handlers: both have a revalidation time specified
& use `no-store` on a fetch, but only one of them specifies `export
const dynamic = 'force-static'`. The one that doesn't specify
`force-static` is correctly omitted from the prerender manifest. The one
that is `force-static` is correctly in the prerender manifest with the
right expiration time. An additional test case was added to verify that
this data refreshes after the specified interval.

Closes NEXT-2764
  • Loading branch information
ztanner authored Mar 12, 2024
1 parent 6464f67 commit 0312d4a
Show file tree
Hide file tree
Showing 4 changed files with 74 additions and 1 deletion.
5 changes: 4 additions & 1 deletion packages/next/src/export/routes/app-route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
49 changes: 49 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 @@ -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([
{
Expand Down Expand Up @@ -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",
Expand Down Expand Up @@ -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": [
Expand Down
Original file line number Diff line number Diff line change
@@ -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 })
}
10 changes: 10 additions & 0 deletions test/e2e/app-dir/app-static/app/route-handler/no-store/route.ts
Original file line number Diff line number Diff line change
@@ -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 })
}

0 comments on commit 0312d4a

Please sign in to comment.