I have a common problem: My header should show "register" for guests or "profile" for users.
The header is in the root layout file, the user state is saved in a cookie.
In a client component, I am reading the cookie and in useEffect change the status. A simplified version will be this:
'use client';
export default function NavRegisterOrProfile() {
const [user, setUser] = useState(false);
useEffect(() => {
const user = getUserCookie();
if (user) {
setUser(user);
}
}, []);
return (
<>
{user ? <div>Profile</div> : <div>Register</div> }
</>
Problem: This creates a hydration error for users (for guests I am not getting an error):
Error: Hydration failed because the initial UI does not match what was rendered on the server
Undesired solution: I can read the cookie on the server side using
import { cookies } from 'next/headers';
export default function Page() {
const nextCookies = cookies();
const theme = nextCookies.get('user');
return '...'
}
But then my entire site (since this is in the root layout) cannot be static site generated (SSG) and moves to SSR. So this is a big performance issue.
Another undesired solution: I can show nothing until the component is mounted, but for SEO reasons I don't want the UI to shift creating a CLS issue. I want the guest version to be the default SSG, and after the component mounts - change to the user version without the hydration issue.
Full actual code:
'use client';
import React, { Suspense, useEffect, useState } from 'react';
import { hookstate, useHookstate } from '@hookstate/core';
import { getUserCookie } from 'helpers/cookieHelper';
import RegisterModal from '../RegisterModal';
import Link from 'next/link';
import UserAvatar from '@shared/user/UserAvatar';
import Image from 'next/image';
export const globalUserState = hookstate(false);
export default function NavRegisterOrProfile() {
const [showRegister, setShowRegister] = useState(false);
const [hasMounted, setHasMounted] = React.useState(false);
const userState = useHookstate(globalUserState);
useEffect(() => {
setHasMounted(true);
const user = getUserCookie(true);
if (user) {
userState.set(() => user);
}
}, []);
const user = userState.get();
return (
<>
{hasMounted && user?.name ? (
<>
<Link href="/profile" id="desktop-nav-profile">
<UserAvatar user={user} />
</Link>
{user?.isAdmin && (
<div className="shadow-md h-12 w-12 ml-4 flex justify-center items-center rounded-full">
<Link href="/admin">
<Image
src="/images/heart.svg"
width={30}
height={26}
alt="admin"
priority
/>
</Link>
</div>
)}
</>
) : (
<>
<strong
className="mx-3 px-1 cursor-pointer hover:underline"
data-cy="header-register"
id="desktop-nav-register"
onClick={() => setShowRegister(true)}
>
Join Us
</strong>
{showRegister && (
<Suspense>
<RegisterModal
closeModalCallback={() => {
setShowRegister(false);
}}
/>
</Suspense>
)}
</>
)}
</>
);
}