I'm trying to store some basic (non-sensitive) user info about the client, such as their display name and profile picture, in react context. The information is stored in localstorage so that it persists even if the client closes/reloads the page, and the context fetches the data from there, where it's displayed in a client component. Functionally, it works fine. However, I'm running into some problems:
In order to avoid a hydration mismatch error, I have to disable SSR for the whole component.
Even if I keep SSR enabled, the content takes much more time to hydrate than other client components, even if I build the app. This layout shift is really bugging me.
My question is, how can I make this info displayed immediately? I haven't seen anyone else running into this problem. If I'm storing it the wrong way, what would be the right way?
I've created a minimum working example of the bug. I tried looking up all kinds of ways to use react context in nextjs, but none of them seem to suggest I'm doing anything wrong. I also tried to reverse-engineer next-themes to figure out how it works there, but I couldn't quite understand it.
Any help would be greatly appreciated!
I have tried to figure out how I can get rid of this delay, but to no avail. I have generally accepted that it's just a consequence of using SSR, and server-rendered pages load even before client components can mount (though this time difference is less pronounced when in production mode). The way next-themes seems to avoid this is by simply preventing the page from being displayed until it detects the html tag and media query.
As a solution, I have implemented a skeleton component to avoid layout shift. I'm still open to solutions in case anyone knows how to make this information load instantly, but I have accepted that that might just not be possible.
I have added an attempt to use Zustand instead context on my codesandbox linked above. It has the same issue - there's still a brief period of time before the content loads. I want to know how users of Zustand counteract this problem. I still haven't found any examples that have successfully solved this problem.
I have found the solution. As Yilmaz mentioned above, since the authentication is handled on the server, the key is to fetch that data in a server-side component such as layout.tsx
. The key for stashing that data client-side, however, is to use a library like zustand, and create a client component that initializes the state as soon as it's loaded like so:
// storeInitializer.tsx
"use client";
import { useRef } from "react";
import { useStringStore } from "./useStringStore";
function StoreInitializer({ string }: { string: string }) {
const initialized = useRef(false);
if (!initialized.current) {
useStringStore.setState({ string });
initialized.current = true;
}
return null;
}
export default StoreInitializer;
// layout.tsx
...
const data = await fetchApi();
useStringStore.setState({ string: data });
return (
<html lang="en" suppressHydrationWarning>
<body className={inter.className} style={{ height: "100vh" }}>
<StoreInitializer string={data} />
{children}
</body>
</html>
);
...
By calling this initializer in layout.tsx
, the api only gets called once, even if we navigate to a different page, and only fetches the api again on a refresh. We can access the data from the zustand store from any component.
I have updated my example to show the working implementation of this. More details on the implementation can be found in this youtube video.