In my application built with Next.js, I have a dynamic page that displays resources at the route /resource/[id]
. When a user edits a resource, such as #5, I want to refresh the cached page at /resource/5
.
I've created an API route in my /pages
directory to handle resource editing. According to what I've read, I should be able to trigger a refresh of the display route using:
response.revalidate(`/resource/${id}/`);
However, this approach is not working as expected, and it's throwing an 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
It seems like Next.js attempts to revalidate through an HTTP request, but perhaps the revalidation endpoint has been moved? The reason for this failure remains unclear to me.
UPDATE: Upon investigating the Next source code, I discovered that the revalidate
function sends a simulated request to the route being refreshed. In the file
node_modules/next/dist/server/router.js
, you can 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}`);
}
The error message originates from the final throw
statement, which puzzles me because when I log urlPath
, it matches the path I'm attempting to refresh (e.g., /resource/5
). Requesting this path via GET (in the browser or Postman) yields a 200 status code rather than a redirect.
To address this, I appended a slash /
to the end of the path:
response.revalidate(`/resource/${id}/`);
This resulted in a similar error, albeit concerning a 200 response instead of a 308:
Error: Invalid response 200
Evidently, the actual status code isn't crucial; the issue lies within the condition
mocked.res.getHeader("x-nextjs-cache") !== "REVALIDATED"
. Although this header setting occurs deep within the router code, I couldn't figure out why the mock request fails to activate it, especially since other requests do reach that part despite isSSG && !this.minimalMode
being true for all.
Furthermore, I attempted to use revalidatePath
from next/cache
:
revalidatePath(`/resource/[id]`);
Unfortunately, this method triggered another 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)
My assumption is that revalidatePath
is designed solely for usage within the /app
directory.
Finally, I stumbled upon response.unstable_revalidate
, supposedly tailored for refreshing dynamic paths:
response.unstable_revalidate(`/resource/${id}/`);
Despite trying this approach, the method does not exist on the response object:
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)