Make data available globally across Server components
Unanswered
Yakutian Laika posted this in #help-forum
Yakutian LaikaOP
Hey guys, I've been working on a Next.js + Supabase App. I have a nested layout, and in every single route (server component) I need to fetch the user. When loading the innermost child route, the user call is fired 3 times, which leads to long loading times. In the docs I've seen that fetch calls are automatically memoized, but it looks like this is not the case when using the supabase client. Is there a way to fetch the user once (maybe in the root layout), and to pass it to all the child routes?
I've also tried using providers, and this works, but obviously only for Client components. Is there a similar pattern for Server components?
I've also tried using providers, and this works, but obviously only for Client components. Is there a similar pattern for Server components?
47 Replies
@Yakutian Laika Hey guys, I've been working on a Next.js + Supabase App. I have a nested layout, and in every single route (server component) I need to fetch the user. When loading the innermost child route, the user call is fired 3 times, which leads to long loading times. In the docs I've seen that fetch calls are automatically memoized, but it looks like this is not the case when using the supabase client. Is there a way to fetch the user once (maybe in the root layout), and to pass it to all the child routes?
I've also tried using providers, and this works, but obviously only for Client components. Is there a similar pattern for Server components?
you can directly use the
If you need also cache with it, you can get use of
What you choose, depends on your needs.
cache function from react. That will fetch every request (so not a real cache), but it will memorize the result for the request. So you request once (get fresh data) and you only fetch once.If you need also cache with it, you can get use of
unstable_cache. That holds an actual cache. What you choose, depends on your needs.
Yakutian LaikaOP
Since I am using the supabase client, which is passed to my getUser function, the method is recomputed every time, since the reference changed, so this doesn't work. Ideally, I want to fetch the user in the layout, and the pass the data down the server components. Is there no other way to achieve this?
@Yakutian Laika Since I am using the supabase client, which is passed to my getUser function, the method is recomputed every time, since the reference changed, so this doesn't work. Ideally, I want to fetch the user in the layout, and the pass the data down the server components. Is there no other way to achieve this?
so you call the getUser function multiple times?
Yakutian LaikaOP
Yes. I need the properties like the userId in multiple sub routes, all of them are Server components
if you put the getUser function inside the react cache function, what will happen?
Yakutian LaikaOP
Is called every time, since this getUser function has one param, which is a supabase client object. Since this object is instantiated every time, the reference changes, and the cache will not work like expected
you said, that you call the getUser function only in server components. So the function will be handled serverside. So the supabase client can also be initialized inside the function. What do you think about that?
Yakutian LaikaOP
Actually, let me try that. Thanks in the meantime
@Yakutian Laika Actually, let me try that. Thanks in the meantime
sure thing. I like to create only one instance of my client (in my case
db) and then I can just import it into my file and can use it wherever I wantof course for you it's not a postgres client. For you it's a supabase client ^^
Yakutian LaikaOP
Creating the supabase client only once works, but I have issues with the cached result of my getUser function. So when wrapping cache around it, it only gets called once when loading my nested layout, that's fine. But when refreshing the page with router.refresh, or invalidating the path after submitting a form to change the name of the user, the data is not updated since the result of the method is still cached
@Yakutian Laika Creating the supabase client only once works, but I have issues with the cached result of my getUser function. So when wrapping cache around it, it only gets called once when loading my nested layout, that's fine. But when refreshing the page with router.refresh, or invalidating the path after submitting a form to change the name of the user, the data is not updated since the result of the method is still cached
Turkish Van
Would You mind sharing code snippet of Your
getUser function?Yakutian LaikaOP
Sure, it's very simple:
const supabase = createServerComponentClient();
async function getUser(): Promise<User | null> {
const { data } = await supabase.auth.getUser();
return data.user;
}
export const cachedGetUser = cache(getUser);The thing is I am currently migrating my project from SvelteKit to Next.js. In SvelteKit, I could simply fetch the user in the root layout, and access this data in all the other components. So I was wondering if something like that is also possible in Next.js, without having to worry about caching functions.
@Yakutian Laika Sure, it's very simple:
const supabase = createServerComponentClient();
async function getUser(): Promise<User | null> {
const { data } = await supabase.auth.getUser();
return data.user;
}
export const cachedGetUser = cache(getUser);
Can you check if the method is called again when using
router.refresh by adding a console.log inside the getUser function?@Yakutian Laika Since I am using the supabase client, which is passed to my getUser function, the method is recomputed every time, since the reference changed, so this doesn't work. Ideally, I want to fetch the user in the layout, and the pass the data down the server components. Is there no other way to achieve this?
Maybe use a middleware to save the user in a session and get from the session instead of getUser
The problem is that you’re calling the getUser function multiple times, right? You could save the authenticated user in a session and get from there instead. Then you wouldn’t be fetching everytime, just reading from session
@Abno The problem is that you’re calling the getUser function multiple times, right? You could save the authenticated user in a session and get from there instead. Then you wouldn’t be fetching everytime, just reading from session
it would be the same problem. It's not the problem "I am called to much my database". It's more the problem on "I called my database, but the cache is not updating after router.refresh"
Oh, I see
Yakutian LaikaOP
Yeah, I added a console.log to my getUser function. But after calling router.refresh, I get the same old properties instead of the new ones (which have been stored in the db successfully)
The function is being called, though
I think it is because React.cache memoizes the function. In every example in React docs, the function always takes some param. I believe since no argument is being passed to cachedGetUser, it is still cached
That’s why wrapping the same function instance with
cache will give you different caches@Yakutian Laika The function is being called, though
when the function is called, (so the console.log get called) then it's more a problem of supabase, right?
@Abno I think it is because React.cache _memoizes_ the function. In every example in React docs, the function always takes some param. I believe since no argument is being passed to cachedGetUser, it is still cached
React cache just memorizes the result for the request. If another request is made, the cache is gone...
@Abno I think it is because React.cache _memoizes_ the function. In every example in React docs, the function always takes some param. I believe since no argument is being passed to cachedGetUser, it is still cached
Turkish Van
Based on the explained behaviour, You are experiencing, I would not say it's a result of React
If You eventually shared a bit more of Your code, we might be able to assist further.
cache function usage (as B33fb0n said). If You eventually shared a bit more of Your code, we might be able to assist further.
@Abno Oh, I’m not op, just trying to help as well lol
Turkish Van
Oh, my bad. Sorry!
happens the best 

@Yakutian Laika Sure, it's very simple:
const supabase = createServerComponentClient();
async function getUser(): Promise<User | null> {
const { data } = await supabase.auth.getUser();
return data.user;
}
export const cachedGetUser = cache(getUser);
is createServerComponentClient from Supabase? Didn’t find anything named that in the docs
https://supabase.com/docs/guides/auth/server-side/nextjs
https://supabase.com/docs/guides/auth/server-side/nextjs
looks like an own function... 🤔 like created by him
yeah, since cache isn’t the issue, maybe that’s doing something that’s breaking the intended behavior
yea maybe... but we don't know when OP is not talking with us 🤷♂️
Yakutian LaikaOP
Sorry guys, was afk for some time.
I renamed the createClient function from utils/supabase/server.ts to createServerComponentClient in my project, but it is the same method as in this tutorial
export function createServerComponentClient() {
const cookieStore = cookies();
return createServerClient<Database>(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
{
cookies: {
get(name: string) {
return cookieStore.get(name)?.value;
},
set(name: string, value: string, options: CookieOptions) {
try {
cookieStore.set({ name, value, ...options });
} catch (error) {
// The `set` method was called from a Server Component.
// This can be ignored if you have middleware refreshing
// user sessions.
}
},
remove(name: string, options: CookieOptions) {
try {
cookieStore.set({ name, value: '', ...options });
} catch (error) {
// The `delete` method was called from a Server Component.
// This can be ignored if you have middleware refreshing
// user sessions.
}
},
},
}
);
}Yakutian LaikaOP
Turns out the default Next.js is causing caching issues within the supabase client: https://github.com/orgs/supabase/discussions/20022
Looks like the combination of adding
export const revalidate = 0; to my server component and using cache for my db calls is working... even though it's more complicated as I'd like it to be...hm.. I guess then you can also add
noStore() to the top and your should be fine as well 🤔Yakutian LaikaOP
One question: By using
export const revalidate = 0; in my layout, is it valid for all the subroutes?I couldn't find anything related to this in the docs
it should be, yea
Yakutian LaikaOP
Thanks guys for your help 👍
Nice
@Yakutian Laika Thanks guys for your help 👍
sure thing. Please mark solution