Caching for Server Side Actions
Unanswered
Cuvier’s Dwarf Caiman posted this in #help-forum
Cuvier’s Dwarf CaimanOP
I'm working with a Next.js application where a server-side component initially fetches data and then passes it to a client-side component. The client component, primarily responsible for rendering a table, allows users to change filters via a dropdown menu. Upon changing a filter, the component refetches the data to update the table accordingly.
The refetch operation calls the same server actions that were used for the initial data load. My challenge lies in caching these server-side actions. I'm not sure where to begin here in the past i just use SWR lib on client
The refetch operation calls the same server actions that were used for the initial data load. My challenge lies in caching these server-side actions. I'm not sure where to begin here in the past i just use SWR lib on client
79 Replies
The refetch operation calls the same server actions that were used for the initial data load. My challenge lies in caching these server-side actions. I'm not sure where to begin here in the past i just use SWR lib on client
Use unstable_cache() to persists the result of a function between requests.
It functions the same as
fetch() where the data returned from fetch is cached for the next request for an amount of time (or forever)@aardani > The refetch operation calls the same server actions that were used for the initial data load. My challenge lies in caching these server-side actions. I'm not sure where to begin here in the past i just use SWR lib on client
Use unstable_cache() to persists the result of a function between requests.
Sure it would cache between requests?
This works when static rendering kicks in, not sure for server actions
ah ok right they updated the doc: https://nextjs.org/docs/app/api-reference/functions/unstable_cache
in my mind unstable_cache was always used for SSR, dynamic stuff ðŸ˜
I think cache is generated in a node asyncstorage
so it is scoped to the request, though it could discuss with the cache that stores the data you got during static rendering too if your function has no dynamic requirements
(damn rereading this kind of sentence I feel sorry for Next beginners...)
while the docs seems to state that unstable_cache will cache across requests
but is it true if the "getUser" demoed in those docs have dynamic requirements?
@Eric Burel but is it true if the "getUser" demoed in those docs have dynamic requirements?
getUser is assumed to be 3rd party data fetching lib that doesnt use fetch because unstable_cache was meant for fetching data that isnt done by using
fetch() (such as prisma.car.findOne(...))@Cuvier’s Dwarf Caiman just to sum it up, you might want to make it clearer whether your data fetching function has dynamic requirements, eg does it use cookies(), headers(), searchParams
@aardani getUser is assumed to be 3rd party data fetching lib that doesnt use fetch because unstable_cache was meant for fetching data that isnt done by using `fetch()` (such as `prisma.car.findOne(...)`)
yes but "cache" is supposed to be scoped to the request, it's generated for each request
initially it was for memoization
@Eric Burel yes but "cache" is supposed to be scoped to the request, it's generated for each request
which cache? there are multiple layers of cache these days.
react cache
different cache....
so unstable_cache is indeed to be considered very different?
unstable_cache is from Next.js, and react's cache is from react
yes, react's cache do get scoped within a single request and yes thats its primary function -> to dedupe calls
the problem is that "and reuse them across multiple requests." is ambiguous, it can mean multiple requests within the same initial user request (memoization) or multiple user requests
but unstable_cache is to cache fetching function to be used or persisted in between request.
I agree
but
OP did mention this
The refetch operation calls the same server actions that were used for the initial data loadwhich indicates that its 2 separate request and not one request with multiple calls
request A: requesting data for page load using function X
request B: requesting data for server action (maybe after a click) using function X
question:
how to cache function X?
request B: requesting data for server action (maybe after a click) using function X
question:
how to cache function X?
that is how i understand the problem
I've sent feedback to the doc because it's hard to have confirmations on this and I couldn't find the time to run experiments
@aardani that is how i understand the problem
yes and to me it's not 100% that unstable_cache does work here
especially if the function has dynamic requirement
It does! its the primary function of unstable_cache
I've been using it since 13, and know some other people who have used it :D
Id argue it is for dynamic requirement, where each function is handled per request at request time
I 100% need to check that out, right now I've used it mostly for the tags option and static revalidation but didn't try it in other scenarios for dynamic data
thanks
(sorry OP @Cuvier’s Dwarf Caiman for hijacking but that's necessary for me to understand that to help people)
not that it would help, but i wrote a scuffed article about it :D
https://alfonsusardani.notion.site/unstable_cache-from-next-cache-f300b3184d6a472ea5282543d50b9f02
https://alfonsusardani.notion.site/unstable_cache-from-next-cache-f300b3184d6a472ea5282543d50b9f02
(sorry, not as fancy as your blog)
Ok ran a small experiment
so indeed
I got the right understanding of "cache"
but "unstable_cache" doesn't work the same
cache is for memoization
unstable_cache for caching
duh that's atrociously confusing
This is also quite dangerous
becasue a badly configured cache key will spread data across users, despite having dynamic requirements
so good recipe to leak data from the first user
@Eric Burel <@566746134178037770> just to sum it up, you might want to make it clearer whether your data fetching function has dynamic requirements, eg does it use cookies(), headers(), searchParams
Cuvier’s Dwarf CaimanOP
Well this kinda segways into my next point, I don't have any auth check on this server action, I read somewhere that it has same auth protection as the page that it's being called on (not really sure what that means)
Haven't tested that yet
Haven't tested that yet
@aardani request A: requesting data for page load using function X
request B: requesting data for server action (maybe after a click) using function X
question:
how to cache function X?
Cuvier’s Dwarf CaimanOP
here is basically the client component setup now-
Works nice, but every time I'm on a filter that isn't "7D" (the initial data load that remains as initData prop) there is a loading time, just feels weird, to have it load instantly when using the interface when the others don't load instantly even after using the dropdown multiple times in a row
channelData is a server actionconst ChannelTableClient = ({
data: initData,
}: {
data: ChannelDataReturn[];
}) => {
const [filter, setFilter] = useState<filterType>("7D");
const [loading, setLoading] = useState(false);
const [currentPage, setCurrentPage] = useState(1);
const [data, setData] = useState<ChannelDataReturn[]>(initData);
const company = useCompany();
const rowsPerPage = 10;
const fetchChannelData = useCallback(
async (newFilter: filterType) => {
setLoading(true);
try {
const response = await channelData({
companyId: company?.companyId,
dateConfig: newFilter,
});
setData(response);
} catch (error) {
console.error("Failed to fetch channel data:", error);
} finally {
setLoading(false);
}
},
[company?.companyId]
);
useEffect(() => {
if (filter !== "7D") {
fetchChannelData(filter);
} else {
setData(initData);
}
}, [filter, fetchChannelData, initData]);
const handleFilterChange = (newValue: string) => {
if (newValue === "1D" || newValue === "7D" || newValue === "30D") {
setFilter(newValue as filterType);
}
};Works nice, but every time I'm on a filter that isn't "7D" (the initial data load that remains as initData prop) there is a loading time, just feels weird, to have it load instantly when using the interface when the others don't load instantly even after using the dropdown multiple times in a row
Then under the hood the server action makes a SQL query with drizzle ORM (basically PostgresJS) - no idea if the cache even gets hit there
The effect is a smell here
you could move that logic into the handleFilterChange I think
and maybe keep an effect just for initialization upon mounting
hmmm
so you are using the Server Action as a data fetching method
so first I am pretty sure that's a scenario where you want to wrap the server action into a transition
then, you'll want to rewrite handleFilterChange so it takes care of firing the fetchChannelData
If you formalize your app as a state machie (see xstate reasionning) this puts side effects of fetching data into "state transitions" which is the right place to do so
But anyway this is not your initial issue, here you want to cache the server action data
So as @aardani rightfully explained, the best approach would be to use "unstable_cache", with a correct cache key
be mindful that it's unstable and I have some doubts about the safety of this function as it currently behaves
(asked for some pro feedback here)
so you could also setup your own "node-cache" to get a better control
this way you can setup your own cache in the backend
you can also cache locally in the browser, by simply storing a map in the component state for each filter
this prevents the same user from firing N request
we use Node cache in the state of JS survey to avoid putting too much pressure on our Redis cache in dynamic pages, it works very well
you can have a TTL and all
Cuvier’s Dwarf CaimanOP
@Eric Burel Thanks for the detailed response. I will look into the unstable cache. But It also seems like this isn't an ideal setup and I wanna use best practice
What is my other alternative? Make the data fetch a normal async function called in the server component parent and also make a normal API route that hit's that same route? Use SWR fetch for ez cache, profit, lol. Idk that was the original dillema that led me to server actions
so you are using the Server Action as a data fetching methodWhat is my other alternative? Make the data fetch a normal async function called in the server component parent and also make a normal API route that hit's that same route? Use SWR fetch for ez cache, profit, lol. Idk that was the original dillema that led me to server actions
Felt like making it was kind of redundant and that server actions we're supposed to help with that
Yes
Jack Harrington suggested you make both data fetching in asyn Server Component and also in runtime using server action for any refetching
@Cuvier’s Dwarf Caiman <@769111741098622976> Thanks for the detailed response. I will look into the unstable cache. But It also seems like this isn't an ideal setup and I wanna use best practice
`so you are using the Server Action as a data fetching method`
What is my other alternative? Make the data fetch a normal async function called in the server component parent **and also** make a normal API route that hit's that same route? Use SWR fetch for ez cache, profit, lol. Idk that was the original dillema that led me to server actions
That's totally fine just the first time I saw it IRL, I only used them for actual actions = form submissions
You don't need client-side fetching with SWR + API in the mix though for data updates, it's replaced by your server actions which is fine