- Published on
Error hydration failed because the initial UI does not match what was rendered on the server in Next.js
- Authors
- Name
- interglobalmedia
- @letsbsocial1
I just discovered (right now) that if I have invalid JSX
in one Next.js
page component, and different JSX
(which is valid let’s say) in another Next.js
page component which is part of the same UI
, I get a hydration error
. Let’s say I have the following in my Home
(page) component
:
// pages/index.js
export default function Home({ users }) {
return (
<div className={styles.container}>
<table id="users">
<tr>
<th>Name</th>
<th>Email</th>
<th>Details</th>
</tr>
{users.map((user) => (
<tr key={user.id}>
<td>{user.name}</td>
<td>{user.email}</td>
<Link
style={{ color: '#4caf50' }}
href={`/users/${user.id}`}
>
View Details
</Link>
</tr>
))}
</table>
</div>
)
}
However, in pages/users/[id].js
, the JSX
looks like the following:
export default function Home({ user }) {
export default function Home({ user }) {
return (
<div className={styles.container}>
<table id='users'>
<tr>
<th>Name</th>
<th>Email</th>
<th>Phone No</th>
<th>website</th>
</tr>
<tr>
<td>{user.name}</td>
<td>{user.email}</td>
<td>{user.phone}</td>
<td>{user.website}</td>
</tr>
</table>
<Link href='/'>
<a style={{ marginTop: '3rem' }}><b>Go Back</b></a>
</Link>
</div>
)
}
When I went to the browser
to check my application
, I saw the following error:
I wasn’t quite sure what was going on, so I went into Chrome Developer Tools
itself to see what error
I was getting there and the following is what was returned:
next-dev.js?3515:20 Warning: Expected server HTML to contain a matching <table> in <div>.
at table
at div
at Home (webpack-internal:///./pages/index.js:17:11)
at MyApp (webpack-internal:///./pages/_app.js:9:11)
at PathnameContextProviderAdapter
This told me immediately that there must be an error in the JSX
, but I still wanted to know why I would get this error
, so I read more about this hydration error
in the Next.js docs
. And the following is what I came up with:
Why This Error Occurred While rendering your application, there was a difference between the React tree that was pre-rendered (SSR/SSG) and the React tree that rendered during the first render in the Browser. The first render is called Hydration which is a feature of React
This can cause the React tree to be out of sync with the DOM and result in unexpected content/attributes being present.
While rendering your application, there was a difference between the React tree that was pre-rendered (SSR/SSG) and the React tree that rendered during the first render in the Browser. The first render is called Hydration which is a feature of React.
This can cause the React tree to be out of sync with the DOM and result in unexpected content/attributes being present.
As for the hydration error (hydrate has been replaced with hydrateRoot in React 18) itself,
React expects that the rendered content is identical between the server and the client. It can patch up differences in text content, but you should treat mismatches as bugs and fix them. In development mode, React warns about mismatches during hydration. There are no guarantees that attribute differences will be patched up in case of mismatches. This is important for performance reasons because in most apps, mismatches are rare, and so validating all markup would be prohibitively expensive.
There were examples
and suggestions
as to how to fix invalid JSX
(HTML
), but I still wanted to delve deeper before making any changes. Honestly, I was wondering whether I would be able to still use the table element
with the nested Link
vs non-nested Link
. It was terribly obvious from
the beginning
that it just was not going to happen. I looked for similar issues/topics
on stackoverflow, and the following is what I found:
If u use html tags u want to place them in correct way and correct order. in NEXTJS. - Bathila Sanvidu answered Oct 26 at 0:47
I even found an issue
on the topic
on the Next.js GitHub repository
. There were many different solutions proposed
there, but what did recur often
was the mention of “invalid html”
.
And then I found another issue
on the Next.js Github repo
entitled React 18 - Avoiding hydration errors, but initialize client-only state directly if possible #23068. But why would I want to keep invalid HTML
just because I am trying to force an issue
? In this case, using a table
(which would not be best practice
for smaller viewports
, because it just would not display like a table
anymore anywway, but more like a single column
if even possible! Tables
are so “1995”
anyway.
I refactored my JSX
to finally look like something decent and which was entirely responsive and a mobile first design
(totally into that approach now), and the following is the JSX`` I ***came up*** with for my
Home (
page)
component`:
export default function Home({ users }) {
return (
<section className={styles.container}>
<article id="users">
{users.map((user) => (
<ul className="user-data" key={user.id}>
<li>Name</li>
<li>{user.name}</li>
<li>Email</li>
<li>{user.email}</li>
<li>Details</li>
<Link
style={{ color: '#4caf50' }}
href={`/users/${user.id}`}
>
View Details
</Link>
</ul>
))}
</article>
</section>
)
}
And the getStaticProps
remained the same:
export async function getStaticProps() {
const res = await fetch('https://jsonplaceholder.typicode.com/users')
const users = await res.json()
return {
props: {
users,
},
}
}
And for the single (dynamic) user
I ended up with the following JSX
:
export default function Home({ user }) {
return (
<section className={styles.container}>
<article id="users">
<ul className="user-title">
<li>Name</li>
<li>Email</li>
<li>Details</li>
</ul>
<ul key={user.id} className="user-data">
<li>{user.name}</li>
<li>{user.email}</li>
<Link style={{ marginTop: '3rem' }} href="/">
<b>Go Back</b>
</Link>
</ul>
</article>
</section>
)
}
And there, I had both getStaticProps
and getStaticPaths
because the path
was dynamic (getting a single user
by id
):
export async function getStaticProps(context) {
const res = await fetch(
`https://jsonplaceholder.typicode.com/users/${context.params.id}`,
)
const user = await res.json()
return {
props: {
user,
},
}
}
export async function getStaticPaths() {
const res = await fetch('https://jsonplaceholder.typicode.com/users')
const users = await res.json()
const ids = users.map((user) => user.id)
const paths = ids.map((id) => ({ params: { id: id.toString() } }))
// paths : { params: { id: '1', id: '2' } }
return {
paths,
fallback: false,
}
}
And both returned JSX(s)
matched and were valid HTML
. And this is how the Home page
rendered:
It might not be a table
, but it is mobile first design
, and is completely responsive. The entire repository on Github
: Next users data fetching repo on GitHub
Happy Next.js JSX matching!