These error messages all essentially say the same thing:
The HTML from the server does not match what is rendered by your app.
Why is this happening?
When teste
is called, it does not always return the same thing. It is designed to display random numbers.
This means it will display a different list of numbers, every time teste
is rendered.
When using Server-Side Rendering (SSR) with a framework like NextJS, it is using React hydration. This is just a fancy word for saying that React is figuring out how to take the DOM structure rendered from the HTML file and convert it into a running single-page app (SPA).
For this to work correctly, React Hydration requires that the HTML from the server and what your client-side app renders are an EXACT match.
So in your scenario, this is what is happening:
- The server needs to generate the HTML file.
- It calls
teste
, which generates a random array of numbers and renders it.
- The server sends this HTML to the client.
- The client app calls
teste
, which generates a NEW random array of numbers and renders it.
- It attempts to "hydrate" the app, but the HTML and client-render do not match.
- NextJS/React doesn't know how to handle this mismatch, so it logs the errors you saw.
How do you fix this?
The fix is simple, but it requires you to be mindful of when it is or isn't necessary.
The common approach to this is to utilize useEffect()
to ensure both the server and client render the same thing during the hydration process, and only render the dynamic content on the client afterwords.
The easiest way to do this is to simply render nothing. Using your code, that would look something like this:
import React from "react";
export default function Teste() {
const [hydrated, setHydrated] = React.useState(false);
React.useEffect(() => {
setHydrated(true);
}, []);
if (!hydrated) {
// Returns null on first render, so the client and server match
return null;
}
let number = numeros();
return number.map((n) => <div key={n}>Number: {n}</div>);
}
What this is doing is ensuring that the first time Teste
renders it will return null
.
This first render is what the server uses to generate the HTML file and is also what the client-side app will use for the "hydration" process.
During this first run, hydrated
will have the default value of false
, which will cause the component to return null
. Also in this first run, useEffect()
will call setHydrated(true)
, which will trigger a second render after the first has completed.
By the time the second render runs, the app will already be hydrated, so there is no need to worry about the errors occurring anymore. At this point hydrated
will be true
, so the randomized numbers will render normally.
More info
If you want to learn more about React hydration I wrote a blog post about fixing these types of errors.
I also published an NPM package that helps simplify dealing with these types of hydration errors: react-hydration-provider
To fix your errors using react-hydration-provider
, your code would look something like this:
import { HydrationProvider, Client } from "react-hydration-provider";
function App() {
return (
<HydrationProvider>
<Client>
<Teste />
</Client>
</HydrationProvider>
);
}
function Teste() {
let number = numeros();
return number.map((n) => <div key={n}>Number: {n}</div>);
}
This would make Teste
only render on the client-side once the app has hydrated.
You could also do something a little more complex like this:
import { HydrationProvider, Server, Client } from "react-hydration-provider";
function App() {
return (
<HydrationProvider>
<Teste />
</HydrationProvider>
);
}
function Teste() {
let number = numeros();
return number.map((n) => (
<div key={n}>
<span>Number: </span>
<Client>{n}</Client>
<Server>Loading...</Server>
</div>
));
}
This would allow your app to initially render a loading message for each of the numbers and then replace it with the random numbers once the app completes the hydration process.