rehypejs / rehype-react

plugin to transform to preact, react, vue, etc
https://unifiedjs.com
MIT License
394 stars 27 forks source link

Hydration failed because the initial UI does not match what was rendered on the server. #43

Closed RyanClementsHax closed 1 year ago

RyanClementsHax commented 1 year ago

Initial checklist

Affected packages and versions

rehype-react 7.1.1

Link to runnable example

https://github.com/RyanClementsHax/ryanclements.dev/tree/server-mismatch-reproduction

Steps to reproduce

yarn dev, open browser to localhost:3000, and see the hydration error

This was reproduced using node v19.2.0

The set up I created in this example is processing content on the server, then converting the generated hast to react nodes on the client.

This error seems caused from plugins that generate certain types of nodes in certain orders. In this example, I made a custom plugin that wraps images in figures here. This will cause the hydration error, but if I wrap the image node in a picture tag, everything is fine. I've looked at the generated nodes on the server and the client and didn't notice any differences between them (although I could be wrong). It should be noted that if raw html is parsed from the markdown using rehype-raw and such, this all works fine.

Thanks for your hard work and awesome libraries :)

P.S. Let me know if this is not the right repo for this

Expected behavior

No hydration error occurs

Actual behavior

Hydration error occurs

Runtime

Node v17, Other (please specify in steps to reproduce)

Package manager

yarn 1, Other (please specify in steps to reproduce)

OS

Linux

Build and bundle tools

Next.js

ChristianMurphy commented 1 year ago

This feels a bit like an XY problem. Taking a couple steps back.

Your end goal appears to be serverside/statically rendering markdown using Next js, correct?

If so, have you considered the @next/mdx integration? https://mdxjs.com/docs/getting-started/#nextjs

It uses unified, remark, and rehype under the hood, supports remark and rehype plugins, and can render both .mdx files and plain .md files.

I'd generally recommend using the pre-built integration/solution, over rolling your own custom static rendering solution. Unless you have a very specific use case for bespoke rendering, which isn't articulated in your issue yet.

RyanClementsHax commented 1 year ago

Thanks for the reply. I've considered those, but wanted to keep the solution lighter and easier for me to understand the inner workings. Also it's been fun learning how to write my own plugins :)

So far everything has been working well until I came by this hydration error which ended up baffling me. To help out, I updated the example repo with a few more additions.

I found out that rehype-react will produce this error based on the hast tree its given.

A figure wrapped in a p will cause the issue, but a figure at the root won't. To check, all you would need to do is replace useReactFromHast(root) with the hast you want to check.

This finding doesn't explain the figure vs picture problem, but it might help?

ChristianMurphy commented 1 year ago

The issue is with the validity of the HTML. figure cannot appear inside a p tag.

Enter the following static HTML into a browser and inspect the result.

<!DOCTYPE html>
<html>
<body>

<p>
  <figure>
    <img src="https://www.w3schools.com/tags/pic_trulli.jpg" alt="Trulli" style="width:100%">
    <figcaption>Fig.1 - Trulli, Puglia, Italy.</figcaption>
  </figure>
</p>

</body>
</html>

note that the browser attempts to fix the invalid HTML. and will produce a document similar or identical to the following:

<!DOCTYPE html>
<html>
<body>

<p></p>

<figure>
  <img src="https://www.w3schools.com/tags/pic_trulli.jpg" alt="Trulli" style="width:100%">
  <figcaption>Fig.1 - Trulli, Puglia, Italy.</figcaption>
</figure>

<p></p>

</body>
</html>

that is likely an issue of your server-side rendered producing invalid HTML, the browser fixing it, and the hydrator being unable to reconcile the difference.

RyanClementsHax commented 1 year ago

Yup! That ended up being the issue! I didn't know the browser would rewrite the dom if it wasn't valid. I had a hard time catching it because The source page didn't show signs of rewriting and react would overwrite whatever the browser rewrote on first render.

I verified this on the reproduction repo linked.

image

Closing this as this isn't an issue, but a product of me not understanding web standards lol

Thanks for the help :)

github-actions[bot] commented 1 year ago

Hi! This was closed. Team: If this was fixed, please add phase/solved. Otherwise, please add one of the no/* labels.

github-actions[bot] commented 1 year ago

Hi! Thanks for reaching out! Because we treat issues as our backlog, we close issues that are questions since they don’t represent a task to be completed.

See our support docs for how and where to ask questions.

Thanks, — bb