Ivan M
Ivan M

Reputation: 997

React 16: Warning: Expected server HTML to contain a matching <div> in <div> due to State

I'm getting the following error using SSR

Warning: Expected server HTML to contain a matching <div> in <div>.

The issue is on the client when checking the browser width on component mount, and then setting the state of a component to render a mobile version of it instead.

But the server is defaulting the desktop version of the container as it is not aware of the browser width.

How do I deal with such a case? Can I somehow detect the browser width on the server and render the mobile container before sending to the client?

EDIT: For now I've decided to render the container when the component mounts. This way, both server and client side render nothing initially preventing this error.

I'm still open to a better solution

Upvotes: 89

Views: 175584

Answers (8)

Jai Singh
Jai Singh

Reputation: 1

In my case, I had to place the "Provider of Redux" and "Toaster" within the body tag in the Root layout.

export default function RootLayout({ children }) {
      return (
        <>
          <html lang="en">
            <body className={inter.className}>
            <Toaster position="top-right" reverseOrder={false} toastOptions={{ duration: 1000 }} />
              <StoreProvider>
                {children}
              </StoreProvider>
            </body>
          </html>
        </>
      )
    }

Upvotes: 0

Jannis Hell
Jannis Hell

Reputation: 775

From https://github.com/vercel/next.js/discussions/17443#discussioncomment-87097

Code that is only supposed to run in the browser should be executed inside useEffect. That's required because the first render should match the initial render of the server. If you manipulate that result it creates a mismatch and React won't be able to hydrate the page successfully.

When you run browser only code (like trying to access window) inside useEffect, it will happen after hydration 👍

Upvotes: 4

aquinq
aquinq

Reputation: 1448

Gatsby

A recent feature flag of gatsby (introduced in v2.28, December 2020) ables to server-side render pages in dev environment.

This flag is set to true by default. In this case, you might see this error message in the console

Warning: Expected server HTML to contain a matching <div> in <div>.

You can disable this flag in gatsby.config.js file :

module.exports = {
  flags: {
    DEV_SSR: false,
  }
}

doc : https://www.gatsbyjs.com/docs/reference/release-notes/v2.28/#feature-flags-in-gatsby-configjs

Upvotes: 22

Ludovic
Ludovic

Reputation: 263

This message can also occurs due to bad code that doesn't render consistent content between your SSR and CSR, thus hydrate can't resolve.

For example SSR returns :

...
<div id="root">
   <div id="myDiv">My div content</div>
</div>
...

While CSR returns :

...
<div id="root">
    <div id="anotherDiv">My other div content</div>
</div>
...

The best solution in this case is not installing libraries or turning off hydrate but actually fix the inconsistency in your code.

Temporaryly removing <script src="/react-bundle-path.js"></script> from index.js can help to compare the exact content rendered by SSR with content rendered by CSR hydrate.

Upvotes: 10

sunknudsen
sunknudsen

Reputation: 7310

The current accepted answer doesn’t play well with TypeScript. Here is what works for me.

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <link rel="shortcut icon" href="/favicon.ico" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
  </head>
  <body>
    <noscript>
      You need to enable JavaScript to run this app.
    </noscript>
    <div id="root"></div>
  </body>
</html>
import React from "react"
import { hydrate, render } from "react-dom"
import BrowserRouter from "./routers/Browser"

const root = document.getElementById("root")
var renderMethod
if (root && root.innerHTML !== "") {
  renderMethod = hydrate
} else {
  renderMethod = render
}
renderMethod(<BrowserRouter />, document.getElementById("root"))

Upvotes: 9

小弟调调
小弟调调

Reputation: 1333

This will solve the issue.

// Fix: Expected server HTML to contain a matching <a> in
const renderMethod = module.hot ? ReactDOM.render : ReactDOM.hydrate;
renderMethod(
  <BrowserRouter>
    <RoutersController data={data} routes={routes} />
  </BrowserRouter>,
  document.getElementById('root')
);

Upvotes: 37

Ben P.P. Tung
Ben P.P. Tung

Reputation: 1284

My solution is to use a middleware like express-useragent to detect the browser user agent.

Then, in the server side, create a viewsize like {width, height} by the following rules

if (ua.isMobile) {
  return {width: 360, height: 480}
}

if (ua.isDesktop) {
  return {width: 768, height: 600}
}

return {width: 360, height: 480} // default, and for bot

Then, it is still somehow a responsive design in SSR.

Upvotes: 1

Martin Ždila
Martin Ždila

Reputation: 3219

HTTP Client Hints could help you with this.

Another interesting article regarding Client Hints.

Upvotes: 1

Related Questions