If you recently transitioned from using React to Next.js, you must've encountered this error "Error: Hydration failed because the initial UI does not match what was rendered on the server". Let's understand why this error usually occurs and how to solve it.
What is Hydration?
In Next.js, the first render of the page takes place on the server. This pre-rendered HTML is then served to the client. On the client side, React takes over this HTML page to make this page interactive(Basically it attaches event handlers to the dom nodes). This is Hydration.
Why does this error occur?
During this process React also has a model of how pre-rendered HTML should look like (so that it can attach event handlers for necessary Js interactivity), if the client version of the initial render is not the same as the pre-rendered HTML from the server, we get this error.
Some scenarios, in which this error can arise:
You are using browser APIs to conditionally render your component/page: In this case, as the browser APIs (localStorage,sessionStorage) are not available on the server, pre-rendered HTML can differ from the client side.
// I will use this atom in the solutions section :) const defaultState={ username:"", id:"", isLoggedIn:false } export const adminState=atom<IAdmin>({ key:"currentAdmin", default: (typeof window !== 'undefined') ? ( localStorage.getItem('user') ? JSON.parse(localStorage.getItem('user') as string) : defaultState ) : defaultState }) //if this state is used to conditionally render, it will cause hydration error
While using this recoil state in the component, a hydration error will arise because on the server HTML will be rendered according to the default state and on the client side HTML will be different due to localStorage.
Your HTML syntax is not correct: In this case, you have probably used JSX in some weird way. some examples :
<p><div>logout</div></p> <p><p>Your text</p></p>
Some browser extensions change the HTML on the client side.
Solutions for this error:
Reconfigure Logic with useEffect:
The server ignores useEffect blocks during rendering the HTML on the server side. useEffect only runs on the client side and has access to all the browser APIs.// I am using the above mentioned state atom const RequireAuth: React.FC<{ children: ReactElement }> = ({ children }) => { const [loader, setLoader] = useState(true); const user = useRecoilValue(userState); const router = useRouter(); useEffect(() => { if (user.isLoggedIn) { setLoader(false); } else router.replace('/login'); }, []); return <>{!loader && children}</>; }; // As user.isLoggedIn depends on localStorage, // we check for it in the useEffect (client side)
Disable SSR on some components which may differ on the client side:
import dynamic from 'next/dynamic'; //code for YourComponent export default dynamic(() => Promise.resolve(YourComponent), { ssr: false });
In the case of timestamps, pre-rendered HTML will be different from the client version. You can suppress the warning in such cases by using suppressHydrationWarning={true} in the element.
Additional Advice: Rather than rendering an entire page on the client side, consider abstracting the pieces that require client-side rendering into separate components. Utilize the above-mentioned methods to render them, incorporating some tweaks from your end.
Note on security: In the above article, I am storing the JWT token in the localStorage which is not a good practice. A better solution may include authenticating using cookies. If you are using cookies, you can try using getServerSideProps() for getting the authentication status on the server side itself.
Connect with me on Twitter | Linkedin | Github
Happy Coding! ๐๐