javascriptnext.jsserver-side-rendering

nextjs: Link element leads to hydration errors


i'm investigating many hydration errors on my page that seem to come from my migration last year from an old next version into the most current 15.x. See Screenshots at the bottom:

Unhandled Runtime Error Expected server HTML to contain a matching <div> in <a>.

and

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

what i understand from these errors is: the clients current html mismatches what we precalculated on the client side.

which is weird because i used the 'use client' statement in the component.

i could nail all of these down into one button component that uses a next/link component. I therefore simplified it as much as possible but no matter how i edit this component, it will throw hydration errors in the console. I tried various ways of no container element, etc:

'use client'
import React from 'react'
import Link from 'next/link'

const ButtonComponent = function () {
  return (
     <button>
      <Link href="/">Button</Link>
    </button>)
}
export default ButtonComponent

however, the moment i get rid of next/link in this situation, all hydration errors are resolved

'use client'
import React from 'react'

const ButtonComponent = function () {
  return (
     <button>
      Button
    </button>)
}
export default ButtonComponent

which is not what i want - this should be a reusable button that gets children and href as props and renders children.

how do i resolve this? Do i have a misconception?

[screenshot of console: Hydration failed]


Solution

  • The reason you're getting this warning: Incorrect nesting of HTML tags

    Let's see what the official documentation says

    Interactive Content cannot be nested (a nested in a tag, button nested in button tag, etc.)

    Interactive content is content that is specifically intended for user interaction => a (if the href attribute is present), audio (if the controls attribute is present), button, details, embed, iframe, img (if the usemap attribute is present), input (if the type attribute is not in the Hidden state), label, select, textarea, video (if the controls attribute is present)

    It does not matter if you marked it as client component during the full page load. An HTML is still rendered on the server to show a non-interactive preview of it during the client side actions (DOM update, attaching event listeners, etc.)

    Try this when you import your component

    import dynamic from "next/dynamic"
    
    const ButtonComponentWithNoSSR = dynamic(
        () => import("../components/ButtonComponent"),// fix the path pls
        { ssr: false }
    )
    
    const Page = () => {
        return (
            <>
                <ButtonComponentWithNoSSR />
            </>
        )
    }
    

    Read more on How are Client Components Rendered?