Thought leadership from the most innovative tech companies, all in one place.

How To Solve Hydration Error In Next.js

Today, let’s talk about hydration error that often occurs in Next.js. Most of us used Next.js must be familiar with the following error: The reason for the error has been clearly stated in the error…

image

Today, let’s talk about hydration error that often occurs in Next.js. Most of us used Next.js must be familiar with the following error:

**Error**: Hydration failed because the initial UI does not match what was rendered on the server.

The reason for the error has been clearly stated in the error message: the **hydration** failed due to the inconsistency between the UI after hydration and the UI rendered by the server. Although the error message says hydration failed, however, it still shows the correct UI that can be considered rendered successful.

Let’s talk about a business case

We stored some unimportant or unsynchronized data locally and get these locally stored data on the client side. For example, we have some configurations stored in localStorage, and the page will be rendered according to the configuration.
Take sidebar as an example:

export default function R() {
  const [expand, setExpand] = React.useState(
    () => localStorage.getItem(EXPAND_STORAGE_KEY) === "1"
  );
  return (
    <div>
      <NavSidebar expand={expand} onExpand={setExpand} />
    </div>
  );
}

We have a sidebar that the user can expand or collapse, we often save its status locally for user experience. As Next.js has client render(CSR) and server render(SSR) both, the code will run correctly on the client side but will result in an error on the server rendering (SSR) because the server side lackslocalStorage API. So we modified the code:

export default function R() {
  const [expand, setExpand] = React.useState(() =>
    typeof window === "undefined"
      ? false
      : localStorage.getItem(EXPAND_STORAGE_KEY) === "1"
  );
  return (
    <div>
      <NavSidebar expand={expand} onExpand={setExpand} />
    </div>
  );
}

We check if it is a server or a browser environment by window keyword, and then run different codes according to it, then the error we mentioned at the beginning will disappear. The above code looks good logically, but the abovehydration error will come out when you run it in the browser.

React Do The Check Instead of Next.js

In fact, the error was caused by a check of react-domin React instead of Next.js. We can simply look at the relevant source code in react-dom:

if (!tryHydrate(fiber, nextInstance)) {
  if (shouldClientRenderOnMismatch(fiber)) {
    warnNonhydratedInstance(hydrationParentFiber, fiber);
    throwOnHydrationMismatch();
  }
}
function throwOnHydrationMismatch(fiber) {
  throw new Error(
    "Hydration failed because the initial UI does not match what was " +
      "rendered on the server."
  );
}
function shouldClientRenderOnMismatch(fiber) {
  return (
    (fiber.mode & ConcurrentMode) !== NoMode &&
    (fiber.flags & DidCapture) === NoFlags
  );
}

The code in react-dom will use tryHydrate to try the hydrate operation, if it fails, the mode and flags will be checked and the hydration error will be thrown.

How to solve this error? There is 3 Solution For you.

Solution 1

useEffect/componentDidMount To solve the above problems, it is officially recommended to use useEffect:

const [expand, setExpand] = React.useState(true);

// to avoid ssr error
useEffect(() => {
  setExpand(localStorage.getItem(EXPAND_STORAGE_KEY) === "1");
}, []);

There is no error will be thrown because the effect will not be executed on the server. Of course, you can also use class components, and then get localStorage in componentDidMount.
However, there are some problems that appear when using this solution. For example, if there is an animation for the expansion and collapse of the sideBar, the user will see an extra animation when entering the page, which will be strange. The solution is not to render the sidebar by default. 😂 Therefore, the effect of SSR is not as good as expected, it really depends on the actual business.

Solution 2

react-no-ssr Another solution is to use some open-source libraries, such as react-no-ssr. In fact, react-no-ssr is also implemented using the solution 1. You can look at the source code:

import React from "react";

const DefaultOnSSR = () => <span></span>;

class NoSSR extends React.Component {
  constructor(...args) {
    super(...args);
    this.state = {
      canRender: false,
    };
  }

  componentDidMount() {
    this.setState({ canRender: true });
  }

  render() {
    const { children, onSSR = <DefaultOnSSR /> } = this.props;
    const { canRender } = this.state;

    return canRender ? children : onSSR;
  }
}

export default NoSSR;

It can be seen that NoSSR will set canRender only in componentDidMount, then it will be rendered in the right way.

Solution 3

Turn Off SSR We can solve the problem by closing SSR of the component as well. In fact, the above react-no-ssr is one of them, but the Next.js also provide a built-in solution: load components dynamically and turn off **SSR**, take the above sidebar as an example:

import dynamic from "next/dynamic";

const DynamicSidebarWithNoSSR = dynamic(() => import("../components/Sidebar"), {
  ssr: false,
});

export default function R() {
  return (
    <div>
      <DynamicSidebarWithNoSSR />
    </div>
  );
}

We only need to extract the component, then use dynamic to load the component and pass in the SSR parameter as false to turn off the server render of the component. Of course, we can do some work for convenience:

import dynamic from "next/dynamic";
import React from "react";

const NoSSR = (props) => <React.Fragment>{props.children}</React.Fragment>;

export default dynamic(() => Promise.resolve(NoSSR), {
  ssr: false,
});

Then we only need to directly call the NoSSR to wrap the child component:

import dynamic from "next/dynamic";
import Sidebar from "../components/Sidebar";

export default function R() {
  return (
    <div>
      <NoSSR>
        <Sidebar />
      </NoSSR>
    </div>
  );
}
Conclusion

CSR is limited to running in the browser and SSR needs to be able to run in both browser and server which meets some problems and challenges that not happened in pure CSR apps. In terms of user experience, SSR will indeed bring a great improvement to our application.




Continue Learning