skip to Main Content

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:

  1. In order to avoid a hydration mismatch error, I have to disable SSR for the whole component.

  2. 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!

Update

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.

Update 2

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.

2

Answers


  1. Your question seems to revolve around the AuthContext component and its interaction with server-side rendering in Next.js.

    The problem arises because the userData value differs between the server and the client, leading to a hydration failure. This failure occurs because React API hydrateRoot() expects the rendered content to match the server-rendered HTML.

    You can refer to the React documentation and the "pitfalls" section at the middle for more details. It’s advised to avoid using checks like typeof window !== 'undefined' or accessing browser-only APIs like localStorage directly in your rendering logic, as it can cause mismatches between server and client snapshots.

    Here’s my workaround:

    1. Initialize the useState hook with a default value of null to ensure consistent initial rendering.
    2. Use the useEffect hook to update the userData after the component mounts.
    3. Remove dynamic imports and directly import ClientComponent.
    4. display something in clientComponent before update

    Here’s the modified AuthContext component:

    import React, { createContext, useContext, useEffect, useState, Dispatch, SetStateAction } from "react";
    
    export default function AuthContext({
      children,
    }: {
      children: React.ReactNode;
    }) {
      const [user, setUser] = useState<string | null>(null); // Initialize with null
      
      // update after component mount
      useEffect(() => {
        const userData = getUser();
        if(userData){
          setUser(userData);
        }
      }, []);
    
      return (
        <Context.Provider value={{ userData: user, setUserData: setUser }}>
          {children}
        </Context.Provider>
      );
    }
    
    export const useAuthContext = () => useContext(Context);
    
    const getUser = () => {
      if(typeof window === 'undefined') return null; // Check for server-side rendering
      let user;
      try {
        user = localStorage.getItem("user");
        if (!user) {
          user = "I want this to be rendered instantly too!";
          localStorage.setItem("user", user);
          return user;
        }
      } catch (e) {}
      return user;
    };
    

    Note this approach might make hydration slower because your components have to render twice. Interfere the user experience on slow connections. The JavaScript code may load significantly later than the initial HTML render, so rendering a different UI immediately after hydration may also feel jarring to the user.

    But again you shouldn’t access browser-only API in render logic.
    Hope this useful to you.

    Login or Signup to reply.
  2. My question is, how can I make this info displayed immediately?

    you cannot do this if you store the user in localstorage because localStorage during the server rendering process is not possible. localStorage is a browser API and does not exist on the server. In server-side rendering, next.js will generate HTML on the server and send it to the client. Hydration is the process where React attaches event listeners to the server-rendered HTML and makes it fully interactive. During this process, you should be assigning user as null, not authenticated. After hydration is completed, you can get the data from the localStorage and update the user.

    If you want to show the user immediately, you should be handling authentication on the server.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search