Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Weird DOM behavior when code splitting #2

Open
Schachte opened this issue Dec 6, 2023 · 0 comments
Open

Weird DOM behavior when code splitting #2

Schachte opened this issue Dec 6, 2023 · 0 comments

Comments

@Schachte
Copy link

Schachte commented Dec 6, 2023

First off, thanks for publishing this repo. It's straight forward and better written than Meta's.

I wanted to post this in case it helps anyone and also get feedback. I am trying to build a pure React SSR setup like yourself but using a bundler (Rollup) for more optimized code splitting on the client (goodbye Next.JS 👋) . I ran into 3 problems with hacks to resolve but wish I didn't have to, so curious if I'm missing something.

  1. When I use the HTML component when the client bundle loads, I get UI hydration mismatch errors from the server. To resolve, I check to see if I'm client and if I am, I only hydrate <App /> and not the surrounding HTML.

Conditional hydration example:

import AppRoutes from "./AppRoutes";
import Html from "./components/Html";

export default function App() {
  if (typeof window !== "undefined") {
    return <AppRoutes />;
  }
  return (
    <Html title="React SSR Demo">
      <AppRoutes />
    </Html>
  );
}
  1. I got non-deterministic behavior with onShellReady when streaming my components that use dynamic imports. On initial page load the DOM would be just a white page and the rendered HTML would say JavaScript not enabled. To resolve I used onAllReady. This is fine for SEO optimized applications for full SSG, but it's not ideal for SSR applications trying to optimize hydration time.

Example SSR handling with Express

app.get("*", async (req, res) => {
  console.log(req.url);
  const stream = ReactDOMServer.renderToPipeableStream(
    <StaticRouter location={req.url}>
      <App />
    </StaticRouter>,
    {
      onAllReady() {
        res.set("content-type", "text/html");
        stream.pipe(res);
        res.end();
      },
      onError() {},
    }
  );
  setTimeout(() => {
    stream.abort();
  }, 5000);
});
  1. I actually have always found the bootstrapScripts on the renderToPipeableStream API to be awkward. It seems, at least to me, it kind of randomly injects the hydration script randomly into the DOM. Unfortunately, when using it here I could only get it to show up after the root html tag which isn't valid.

I removed that line completely and just modified the Html component like so:

import { PropsWithChildren } from "react";

type HtmlProps = PropsWithChildren<{ title: string }>;

export default function Html({ children, title }: HtmlProps) {
  return (
    <html lang="en">
      <head>
        <meta charSet="utf-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1" />
        <meta name="theme-color" content="#000000" />
        <meta
          name="description"
          content="Web site created using create-react-app"
        />
        <title>{title}</title>
      </head>
      <body>
        <noscript
          dangerouslySetInnerHTML={{
            __html: `<b>Enable JavaScript to run this app.</b>`,
          }}
        />
        <div id="root">{children}</div>
        <script defer async type="module" src="/index.js"></script>
      </body>
    </html>
  );
}

I'll try and post a repo soon for complete repro, but open to any feedback or code!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant