最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - Remix Hydration failed: UI on server and client do not match - Stack Overflow

programmeradmin4浏览0评论

It's fine locally (known warning and CSS renders well), but on Vercel my Remix app gets this error:

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

Business logic runs fine but CSS is utterly broken.

Update 26 June 2022, 15:50

I started a new project from scratch and added dependencies one by one and deploying to Vercel at each step. No errors. Styled ponents render well. So the dependencies are not the problem.

I then started fetching data piece by piece from my database through the loader and rendering them in styled ponents one by one. The one thing that consistently breaks the CSS and produces the error is the conversion of a datetime object to a string before rendering:

const DateTimeSpan = styled.span`
  font-size: 1rem;
`;

const hr = now.getHours();
const min = now.getMinutes();

<DateTimeSpan>
  {`${hr}:${min}`}
</DateTimeSpan>

Curiously, it's only when I format it to render only time that it breaks. With date, it's fine:

const yr = now.getFullYear();
const mth = now.getMonth();
const dd = now.getDate();

<DateTimeSpan>
  {`${yr}-${mth}-${dd}`}
<DateTimeSpan>

I'm at a loss to explain this.

Update 2 July 2022, 21:55

Using the same simplest project above, friend and I have determined that CSS with styled ponents breaks when we try to render hours, i.e.:

const hr = now.getHours();

<DateTimeSpan>
  {hr}
</DateTimeSpan>

Our suspicion is styled ponents breaks because hours is rendered in UTC time on the server but locale time on the client.

I'm not sure if this is a bug or if we're supposed to handle this ourselves. Also not sure if this should be asked on Remix or Styled ponents GitHub issues. I've opened an issue on Remix as well anyway for a start.

Original post

Not sure but could be related to these issues:

  • Remix 2570
  • Remix 2947
  • React 24523

I read through the above and a few other pages and all I could think to do was update some dependencies. Here are the possibly relevant ones:

{
"react": "^18.2.0",
"styled-ponents": "^5.3.5"
"@remix-run/node": "^1.6.1",
"@remix-run/react": "^1.6.1",
"@remix-run/vercel": "^1.6.1",
"@vercel/node": "^2.2.0",
}

My main suspicion is it has to do with styled-ponents since I've had similar issues on Nextjs before. But my app/root.tsx and app/entry.server.tsx follow this example for styled-ponents very closely:

// app/root.tsx

export default function App() {
  const data = useLoaderData();

  return (
    <Html lang="en">
      <head>
        ...
        {typeof document === "undefined" ? "__STYLES__" : null}
      </head>
      <Body>
        ...
      </Body>
    </Html>
  );
}
//app/entry.server.tsx

export default function handleRequest(
  request: Request,
  responseStatusCode: number,
  responseHeaders: Headers,
  remixContext: EntryContext
) {
  const sheet = new ServerStyleSheet();

  let markup = renderToString(
    sheet.collectStyles(
      <RemixServer context={remixContext} url={request.url} />
    )
  );
  const styles = sheet.getStyleTags();
  markup = markup.replace("__STYLES__", styles);
  responseHeaders.set("Content-Type", "text/html");

  return new Response("<!DOCTYPE html>" + markup, {
    status: responseStatusCode,
    headers: responseHeaders,
  });
}

The biggest difference with the example seems to be that instead of using hydrate, I use hydrateRoot as we should for React 18. Not sure if it has any bearing on the problem:

// app/entry.client.tsx

import { RemixBrowser } from "@remix-run/react";
import { hydrateRoot } from "react-dom/client";

hydrateRoot(document, <RemixBrowser />);

The Remix docs on CSS-in-JS libraries says: "You may run into hydration warnings when using Styled Components. Hopefully this issue will be fixed soon." The issue hasn't been resolved so maybe this problem doesn't have a solution yet.

But if the example repo works, then maybe I missed something?

It's fine locally (known warning and CSS renders well), but on Vercel my Remix app gets this error:

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

Business logic runs fine but CSS is utterly broken.

Update 26 June 2022, 15:50

I started a new project from scratch and added dependencies one by one and deploying to Vercel at each step. No errors. Styled ponents render well. So the dependencies are not the problem.

I then started fetching data piece by piece from my database through the loader and rendering them in styled ponents one by one. The one thing that consistently breaks the CSS and produces the error is the conversion of a datetime object to a string before rendering:

const DateTimeSpan = styled.span`
  font-size: 1rem;
`;

const hr = now.getHours();
const min = now.getMinutes();

<DateTimeSpan>
  {`${hr}:${min}`}
</DateTimeSpan>

Curiously, it's only when I format it to render only time that it breaks. With date, it's fine:

const yr = now.getFullYear();
const mth = now.getMonth();
const dd = now.getDate();

<DateTimeSpan>
  {`${yr}-${mth}-${dd}`}
<DateTimeSpan>

I'm at a loss to explain this.

Update 2 July 2022, 21:55

Using the same simplest project above, friend and I have determined that CSS with styled ponents breaks when we try to render hours, i.e.:

const hr = now.getHours();

<DateTimeSpan>
  {hr}
</DateTimeSpan>

Our suspicion is styled ponents breaks because hours is rendered in UTC time on the server but locale time on the client.

I'm not sure if this is a bug or if we're supposed to handle this ourselves. Also not sure if this should be asked on Remix or Styled ponents GitHub issues. I've opened an issue on Remix as well anyway for a start.

Original post

Not sure but could be related to these issues:

  • Remix 2570
  • Remix 2947
  • React 24523

I read through the above and a few other pages and all I could think to do was update some dependencies. Here are the possibly relevant ones:

{
"react": "^18.2.0",
"styled-ponents": "^5.3.5"
"@remix-run/node": "^1.6.1",
"@remix-run/react": "^1.6.1",
"@remix-run/vercel": "^1.6.1",
"@vercel/node": "^2.2.0",
}

My main suspicion is it has to do with styled-ponents since I've had similar issues on Nextjs before. But my app/root.tsx and app/entry.server.tsx follow this example for styled-ponents very closely:

// app/root.tsx

export default function App() {
  const data = useLoaderData();

  return (
    <Html lang="en">
      <head>
        ...
        {typeof document === "undefined" ? "__STYLES__" : null}
      </head>
      <Body>
        ...
      </Body>
    </Html>
  );
}
//app/entry.server.tsx

export default function handleRequest(
  request: Request,
  responseStatusCode: number,
  responseHeaders: Headers,
  remixContext: EntryContext
) {
  const sheet = new ServerStyleSheet();

  let markup = renderToString(
    sheet.collectStyles(
      <RemixServer context={remixContext} url={request.url} />
    )
  );
  const styles = sheet.getStyleTags();
  markup = markup.replace("__STYLES__", styles);
  responseHeaders.set("Content-Type", "text/html");

  return new Response("<!DOCTYPE html>" + markup, {
    status: responseStatusCode,
    headers: responseHeaders,
  });
}

The biggest difference with the example seems to be that instead of using hydrate, I use hydrateRoot as we should for React 18. Not sure if it has any bearing on the problem:

// app/entry.client.tsx

import { RemixBrowser } from "@remix-run/react";
import { hydrateRoot } from "react-dom/client";

hydrateRoot(document, <RemixBrowser />);

The Remix docs on CSS-in-JS libraries says: "You may run into hydration warnings when using Styled Components. Hopefully this issue will be fixed soon." The issue hasn't been resolved so maybe this problem doesn't have a solution yet.

But if the example repo works, then maybe I missed something?

Share Improve this question edited Jul 19, 2022 at 11:22 nusantara asked Jun 25, 2022 at 14:14 nusantaranusantara 1,2662 gold badges17 silver badges41 bronze badges 3
  • if I were to take a wild guess its because the variable you were trying to print is now; which would change between server- and client-render as it contains time (with milliseconds). The now on server would not be the same as the now on client-side – boop_the_snoot Commented Jul 4, 2022 at 19:28
  • This explains why the error is only happening in the evening for me. What a PITA. Thanks for taking the time to carefully document this. – Jon Crowell Commented Aug 2, 2022 at 3:45
  • Well written question with good updates along the way. – MEMark Commented Sep 23, 2022 at 7:36
Add a ment  | 

1 Answer 1

Reset to default 3

Yes, rendering hours in particular is the issue, since server time is in UTC and client time is whatever the locale time is (UTC + X hours). This causes UI to be different on both.

One quick way to check this out is by setting the timezone for our current CLI instance to UTC before running the app and trying out its pages:

export TZ=UTC

npm run dev

We'll see that the CSS breaks as described in the problem above.

There're several ways to fix this, specific to different use cases. One is to not send a datetime object. Instead, send it as a string. For instance:

const now: Date = new Date()

// Locale time as example only, we need to know client's locale time
const time: string = now.toLocaleTimeString([], {
               hour: "2-digit",
               minute: "2-digit",
             })

// Send time string to client.

This assumes that we already know the client's time zone so we can use that to set/format time on the server.

A more flexible way is to set the time only after the page has mounted. For instance:

const [now, setNow] = useState<Date>();
const loaderData = useLoaderData<string>();

useEffect(() => {
  if (!loaderData) {
    return;
  }

  setNow(JSON.parse(loaderData));
}, [loaderData]);

return <>{now.toLocaleTimeString([], {
            hour: "2-digit",
            minute: "2-digit",
          })}</>

With this solution, we lose some of the benefit of SSR. This has implications. For instance, we'll need to pay special attention to SEO (view page source, we won't see the date properly rendered). Robots won't be able to index the app properly if we don't.

发布评论

评论列表(0)

  1. 暂无评论