Server components, data fetching, revalidating - at a loss!
Unanswered
Velvet ant posted this in #help-forum
Velvet antOP
I'm using the App router in v13 for the first time.
My page displays a list of components. They need to be editable, but will only occasionally be changed - so ideal for data-caching.
Following the documentation, my structure is:
* a server component
* a client component
When an edit is made in
* the
* the endpoint calls
* there's an artificial pause of 200ms before the enpoint returns
* on success,
All of which, as I understand from the docs, should mean that the data in
This does not work.
If I'm running in dev mode the endpoint updates (which I can see by calling it in a browser) but the UI doesn't. I can fix that by also adding a
... but with a build (so on Vercel) the only thing that works is adding a
So: am I missing something/going down the wrong path?!
My page displays a list of components. They need to be editable, but will only occasionally be changed - so ideal for data-caching.
Following the documentation, my structure is:
* a server component
<DataWrapper /> (see below) which fetches component data and passes it to* a client component
<UIWrapper /> which displays the component listimport 'server-only';
import UIWrapper from "./UIWrapper";
async function getData() {
const res = await fetch(`${process.env.NEXT_URL}/api/component-info/getall`);
if (!res.ok) {
console.error('Failed to fetch data - error:\n', res);
return [];
}
return res.json();
}
export default async function DataWrapper() {
const data = await getData();
return (
<UIWrapper data={data} />
)
}When an edit is made in
<UIWrapper />:* the
<UIWrapper /> fires a PUT to another api endpoint that updates the database* the endpoint calls
revalidatePath() on the getall route used in <DataWrapper />* there's an artificial pause of 200ms before the enpoint returns
* on success,
<UIWrapper /> calls router.refresh() to cause the page to updateAll of which, as I understand from the docs, should mean that the data in
<DataWrapper /> is refreshed and the updated info will show. in <UIWrapper />.This does not work.
If I'm running in dev mode the endpoint updates (which I can see by calling it in a browser) but the UI doesn't. I can fix that by also adding a
{ next: { tags: [] } } to the fetch in <DataWrapper /> and calling revalidateTag alongside revalidatePath ...... but with a build (so on Vercel) the only thing that works is adding a
{cache: 'no-store' } to prevent the fetch being cached at all. Whilst that works, it's a huge waste of database time to constantly make calls when the data rarely changes.So: am I missing something/going down the wrong path?!
31 Replies
Although we don't recommend calling route handlers from server components, it's confirmed that
https://github.com/vercel/next.js/issues/49778 - Closed, but still having problem in server actions
https://github.com/vercel/next.js/issues/49387 - Dynamic route doesn't work
A common workaround is to use
revalidatePath doesn't perform as expected.https://github.com/vercel/next.js/issues/49778 - Closed, but still having problem in server actions
https://github.com/vercel/next.js/issues/49387 - Dynamic route doesn't work
A common workaround is to use
revalidationTag instead of revalidatePath (as you have mentioned), or use res.revalidate in API Routes (Pages Router).For example, our fourm is also using API Routes to revalidate pages.
https://github.com/rafaelalmeidatk/nextjs-forum/blob/main/apps/web/pages/api/revalidate-home.ts
https://github.com/rafaelalmeidatk/nextjs-forum/blob/main/apps/web/pages/api/revalidate-home.ts
Velvet antOP
OK, so two questions:
1. What is the recommended method with server components? I'm querying data from a (Vercel Postgres) db which I want to be able to cache and revalidate (it's likley that the data won't change week-to-week but I want to be able to change it and see the changes appear 'real-time' in the UI). I assume I could use an SQL query in my
getData() function rather than a fetch - but presumably that would mean it's not cached by Next?Velvet antOP
2. In testing to this point it appeared that to get a working version in the dev server I needed to both
revalidateTag in the server component and revalidatePath for the route handler (otherwise the actual SQL query from the route handler was being cached). If I move the route handler into the Pages router rather than the App router does that solve that issue? Otherwise I need revalidatePath anyway ...3. (I can't count) although using
revalidateTag for the server component and revalidatePath for the route handler worked in the dev server, when built/deployed it wasn't casuing the page to be re-built(I had something semi-working about 8 hours ago by calling
revalidatePath on both the route handler (refresh the GET query) and the page path, but somehow in the last hour that's also stopped working and the server component appears to not be refreshing at all - frustrating!)1. Yes, SQL queries won't be cached by Next.js
For now, It is possible with
2. Correct,
3. Yep, it is quite frustrating. [Some people pointed it out](https://github.com/vercel/next.js/issues/49387#issuecomment-1567289235) in the issue
For now, It is possible with
import { unstable_cache } from 'next/cache', but it's undocumented and we don't sure if it will be changed.2. Correct,
res.revalidate in Pages Router works as expected3. Yep, it is quite frustrating. [Some people pointed it out](https://github.com/vercel/next.js/issues/49387#issuecomment-1567289235) in the issue
Velvet antOP
OK, so whilst I'm sure it's on the list of 'stuff to fix' it's obviously not an easy fix given it's been known for 3 months and not patched yet. I'm not keen on using
{ unstable_cache } (have enough headaches without adding experimental features!). It sounds as though an option (accepted not 'the recommened way') would be:1.
<DataWrapper /> makes a fetch to a route handler in the Pages router2. When the
<UIWrapper /> wants to edit entries it sends the request to a route handler in the Pages router3. That handler can
res.revalidate (the path to the page that is rendering <DataWrapper />? think it would be super helpful if the docs were clearer about whether 'path' refers to a path that's being fetched, path to a component, or path to a page that's being served!)Would I also (and presumably first) need to
revalidateTag for a tag on the getData function in <UIWrapper />?And to access the Pages router is there any change needed, eg in
next.config.js?path refers to the path to a page, like /home or /about.revalidateTag still works for revalidating tags, so you might need one if you want to revalidate a specific tag. However, since UIWrapper is a client component, it won't work.Also, you don't need any changes in order to use Pages Router.
Velvet antOP
(just looking at the Pages Router version of the docs, much clearer there!)
Velvet antOP
So (apologies, I really want to get this straight in my head - I need to repeat the pattern several times and don't want to have re-write all of them!)
* assume that the
* does moving the
* calling
* the important one - does
* assume that the
DataWrapper and it's children are being rendered at /components inside my app* does moving the
getall route handler into Pages rather than App router prevent the actual SQL query result from being cached? If not, how do I prevent it? (guessing that having the router handler accept a POST or PUT method rather than GET would do it, but that feels icky)* calling
res.revalidate("/components") will mark that page path as outdated; assume still need to call router.refresh() in the UI once the 'update' route handler has returned in order for the UI to update?* the important one - does
res.revalidate cause the results of getData in the DataWrapper to be revalidated as well? If not, given you're saying I can't revalidate a tag (inside a route handler in the Pages router that's called from UIWrapper client component?) is there any way to cause that data to be revalidated? Or am I back to using cache: 'no-store again?1. Pages Router doesn’t have cache
2.
In addition, I want to clarify that SQL queries are not cached (if they not use fetch), but your page is cached. So revalidating the page can also update the result of SQL query
2.
res.revalidate revalidates your page, including all the fetch in the server components of your page. In addition, I want to clarify that SQL queries are not cached (if they not use fetch), but your page is cached. So revalidating the page can also update the result of SQL query
Velvet antOP
So (using the App Router for building the page, and Page Router only for any API) I could call an SQL query direcly in the
DataWrapper server component; that component (or the page it's in) is cached; and res.revalidate from my (Page router) API will cause the page to revalidate - so effectively that achieves caching the data?Yep
Velvet antOP
Great, there must be something in that lot that'll get me an answer!
Velvet antOP
One last question (sorry) — one of the issues that I've been struggling with has been ensuring the paths (or tags) are revalidated before the
router.refresh() is called , since revalidateTag/revalidatePath in the App router don't return anything. From the docs it looks like res.revalidate is async-friendly so I can await it and then be certain that the data is invalidated by the time that UIWrapper gets a response?Well I didn't test it yet so I can't give you an answer.
Technically it should be revalidated after
Technically it should be revalidated after
router.push because slate data must be revalidated before shipping it to the client.I can only confirm that
revalidatePath/tag will make the next request to be MISSING, which means it happens instantly.so you can always ensure the page is revalidated after
router.push, client can receive the latest dataVelvet antOP
(Ongoing) Have re-written/moved my API endpoints into Pages router. At the moment have made no further changes at all to the page.
Clear out the cache, start dev server, the
If I then try to make an edit — say I'm deleting an item — my
The delete is successful, but the endpoint then errors out at the
So: is there something else (obvious) that I should be doing/adding to the components in the App Router? Do I need to move the page to the Pages Router and look at using
DataWrapper, UIWrapper etc.Clear out the cache, start dev server, the
getall endpoint returns data and the page (/component-info/ashp) shows it, so far so good.If I then try to make an edit — say I'm deleting an item — my
deleteone/[id] endpoint is called. Looks like:import { sql } from '@vercel/postgres';
export default async function handler(request, response) {
if (request.method !== 'DELETE') {
return response.status(405).json({ message: 'Method Not Allowed' })
}
try {
await sql`DELETE FROM ashps WHERE id = ${id};`
await response.revalidate('/component-info/ashp');
return response.status(200).json({ message: 'deleted' });
} catch (error) {
// ... error handling in here
}
}The delete is successful, but the endpoint then errors out at the
revalidate with Error: Invalid response 200
...
Error: Failed to revalidate /component-info/ashp: Invalid response 200
...So: is there something else (obvious) that I should be doing/adding to the components in the App Router? Do I need to move the page to the Pages Router and look at using
getStaticProps (at which point I guess I may as well just revert to the Pages Router entirely for this project)?Remove
status:await res.revalidate('/')
res.json({ revalidated: true })Velvet antOP
That's cutting out the second error, but still getting the first as below (with limited stack trace in case that helps)
Error: Invalid response 200
at Object.revalidate (....../node_modules/next/dist/server/lib/router-server.js:122:23)
at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
at async Server.<anonymous> (./node_modules/next/dist/server/lib/server-ipc/index.js:56:30)Weird, do you sure you’re following the docs?
https://nextjs.org/docs/pages/building-your-application/data-fetching/incremental-static-regeneration#using-on-demand-revalidation
https://nextjs.org/docs/pages/building-your-application/data-fetching/incremental-static-regeneration#using-on-demand-revalidation
Velvet antOP
The docs for ISR only (?) appear in the Pages Router docs and concentrate on causing
getStaticProps to run - which doesn't sound as though it's necessarily compatible with the App Router and server componentsVelvet antOP
The solution: switch to Pages Router, use ISR, all good