Published on

Error hydration failed because the initial UI does not match what was rendered on the server in Next.js

Last Modified on
Last modified on
Authors
 Error hydration failed because the initial UI does not match what was rendered on the server in Next.js
Photo by Pixabay on Pexels

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:


Next.js hydration error
Photo by Interglobalmedia on Interglobalmedia

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:

React Hydration Error

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:


Valid JSX
Photo by Interglobalmedia on Interglobalmedia

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!