I have a Next.js application with a dynamic page for displaying resources: /resource/[id]
. Whenever a user edits (say) resource #5, I want to regenerate the page /resource/5
in Next's cache.
I have an API route (in my /pages
directory) that handles the resource editing. From what I've read, I should be able to make it refresh the display route by doing:
response.revalidate(`/resource/${id}/`);
However, that doesn't work; I get the error:
Error: Failed to revalidate /resource/2153/: Invalid response 200
at revalidate (/home/jeremy/GoblinCrafted/next/node_modules/next/dist/server/api-utils/node.js:388:15)
at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
- error unhandledRejection: Error: Failed to revalidate /resource/2153/: Invalid response 200
at revalidate (/home/jeremy/GoblinCrafted/next/node_modules/next/dist/server/api-utils/node.js:388:15)
at process.processTicksAndRejections (node:internal/process/task_queues:95:5) {
digest: undefined
I guess Next is trying to revalidate through an HTTP request, but the revalidation endpoint moved? I'm not exactly sure why this fails.
EDIT: I dug into the Next source code, and it seems like revalidate
is just making a mock request to the route that is being revalidated. If you look at node_modules/next/dist/server/router.js
you'll see:
async revalidate({ urlPath , revalidateHeaders , opts }) {
const mocked = (0, _mockrequest.createRequestResponseMocks)({
url: urlPath,
headers: revalidateHeaders
});
const handler = this.getRequestHandler();
await handler(new _node.NodeNextRequest(mocked.req), new _node.NodeNextResponse(mocked.res));
await mocked.res.hasStreamed;
if (mocked.res.getHeader("x-nextjs-cache") !== "REVALIDATED" && !(mocked.res.statusCode === 404 && opts.unstable_onlyGenerated)) {
throw new Error(`Invalid response ${mocked.res.statusCode}`);
}
My error is coming from that throw
at the end ... but that doesn't make any sense to me, because when I log urlPath
, it's just the path I'm trying to refresh (eg. /resource/5
). When I try to hit that path with a GET request (either in my browser or through Postman) I don't get a redirect: I get a 200.
This led me to try adding a /
to the end of my path:
response.revalidate(`/resource/${id}/`);
That got me a similar error, only this time it was for a 200 instead of a 308:
Error: Invalid response 200
So, it seems my status code doesn't actually matter: it's the mocked.res.getHeader("x-nextjs-cache") !== "REVALIDATED"
part that's the problem. However, digging through the code, I only found one place that sets that header. It happens within a function within a function within a renderToResponseWithComponentsImpl
function in node_modules/next/dist/esm/server/base-server.js
:
if (isSSG && !this.minimalMode) {
// set x-nextjs-cache header to match the header
// we set for the image-optimizer
res.setHeader("x-nextjs-cache", isOnDemandRevalidate ? "REVALIDATED" : cacheEntry.isMiss ? "MISS" : cacheEntry.isStale ? "STALE" : "HIT");
}
... but when I add console.log
statements it seems that code isn't being reached ... even though I see lots of other requests reaching it, and isSSG && !this.minimalMode
is true for all of them.
So, in short, somehow the mock request revalidate
makes isn't triggering the setting of that header, which then makes it fail ... but I have no clue why it's not getting to that header-setting code, because it's so deeply nested in the router code.
END EDIT
I also tried using revalidatePath
, from next/cache
:
revalidatePath(`/resource/[id]`);
but that also gives an error:
revalidatePath(`/resource/[id]`);
Error: Invariant: static generation store missing in revalidateTag /resource/[id]
at revalidateTag (/home/me/project/next/node_modules/next/dist/server/web/spec-extension/revalidate-tag.js:15:15)
at revalidatePath (/home/me/project/next/node_modules/next/dist/server/web/spec-extension/revalidate-path.js:13:45)
at handler (webpack-internal:///(api)/./pages/api/resource.js:88:67)
at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
I think this is because revalidatePath
is only intended to be used in the /app
directory?
Finally, I found a reference to a response.unstable_revalidate
method, which seemed to be designed for revalidating dynamic paths:
response.unstable_revalidate(`/resource/${id}/`);
... but when I try to use it, it isn't there on the response:
TypeError: response.unstable_revalidate is not a function
at handler (webpack-internal:///(api)/./pages/api/resource.js:88:18)
at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
from How Can I Manually Revalidate a Dynamic Next.js Route, From an API Route?
No comments:
Post a Comment