Next.js: Delaying the initial client-side hydration

Created on 28 Mar 2019  ·  9Comments  ·  Source: vercel/next.js

Feature request

Is your feature request related to a problem? Please describe.

My use case is that we have a 1MB json file that is needed to render the page. No problem on the server, but we don't want to send this data to client from getInitialProps as it'll make the pagesize huge.

So, we want to SSR initially, then load the JSON file via ajax on the client and only then trigger the initial client-side hydration/render once the file has loaded.

Describe the solution you'd like

A way to supply a promise to next/client that can awaited before the client-side hydration takes place.

Describe alternatives you've considered

If we cannot wait to delay the render, the initial SSR completes, then we have to replace it with a loading spinner on the client side while the file finishes loading. That's obviously not ideal, as it means that:

  • The content renders server-side.
  • Then it disappears on the client initial render, replaced by a spinner.
  • And then returns as before, on a client later render after the file has loaded.

You can imagine how that gives a pretty confusing experience for the user.

I've got it working by either:

  • Directly adding another await call inside the next/client/index render method in node_modules
  • Using a hacky innerHTML workaround (see comment below),

Both these ways have problems, and so I'd prefer a real solution.

Additional context

I'm happy to do a PR if you can suggest me on the right approach to take.

Most helpful comment

Another case for delaying hydration is conditional loading of polyfills from chunks; my attempt to inject it via Webpack customization has failed (see #8189).

All 9 comments

Not saying this feature request is invalid, but here's some thoughts:

  1. Is there any way to slim down this json file? Maybe it can be optimized to only include things that actually appear in the page? Does it contain things like base64 encoded images that can be moved out of it?
  2. Did you check gzipped size of the page with and without the json? If 1. is satisfied, then the json and the html should contain a lot of duplicate strings, which compresses really well. Page size would be quite big, but for network overhead, I guess gzipped size is what matters. This wouldn't help with parsing overhead ofcourse, but you seem to be primarily concerned with network overhead.

Also something to consider: as long as Next's <script type="application/json">props</script> tag is placed after the server-rendered HTML and bundle script tags, the document will render before it has finished loading. If you're seeing a white screen until the entire document has been streamed, it's likely the effect of including some other inline <script> tag (explanation).

Thanks for your replies 😊

Not saying this feature request is invalid, but here's some thoughts:

  1. Is there any way to slim down this json file? Maybe it can be optimized to only include things that actually appear in the page? Does it contain things like base64 encoded images that can be moved out of it?

This would be great and the best solution, but unfortunately this current structure is relied upon all through our app. There’s much legacy code that depends on it like this. It's full of category and product data, no base64. I’m not able to refactor it to the extent that’ll make a difference in the foreseeable future.

  1. Did you check gzipped size of the page with and without the json? If 1. is satisfied, then the json and the html should contain a lot of duplicate strings, which compresses really well. Page size would be quite big, but for network overhead, I guess gzipped size is what matters. This wouldn't help with parsing overhead ofcourse, but you seem to be primarily concerned with network overhead.

That's a good point about the gzip, I will investigate more about that. However the page size still appears > 1MB in the network tools, Lighthouse complains about it and we’re worried about the effect on SEO and bounce rates if the initial payload is massive. Maybe we could get away with it if we had to, but it’d be nice not to have to compromise.

Another alternative we’ve tried is an ugly workaround:

  • In _app.js, wrap page component with a <div id=“next-app”> </div>
  • On SSR, render as normal.
  • On first client side render, get the innerHTML of #next-app, then return a new JSX element with that innerHTML using dangerouslysetinnerhtml. This essentially cancels the first client-side render and tries to keep what the server rendered instead.
  • Once the file finishes loading (await'ed in componentDidMount), let the client render as normal

It works, but React gives a mismatch warning about it, and it basically defeats the point of having hydration in the first place.

Also something to consider: as long as Next's <script type="application/json">props</script> tag is placed after the server-rendered HTML and bundle script tags, the document will render before it has finished loading. If you're seeing a white screen until the entire document has been streamed, it's likely the effect of including some other inline <script> tag (explanation).

Thanks for the info, but unless I'm misunderstanding something I don't think this isn’t the case here and I don't see a white screen. The basic problem for me is that Next triggers the client-side render immediately, and I need a way to make it to wait for a file to load first. It happens even with no other script tags in there.

Another case for delaying hydration is conditional loading of polyfills from chunks; my attempt to inject it via Webpack customization has failed (see #8189).

Closing this as it's a non-goal for the project, and likely not necessary due to the comment in https://github.com/zeit/next.js/issues/6817#issuecomment-477655408.

If you see a white screen, double check your network trace for custom-injected scripts or CSS.

Same issue here with runtime styled-components theming.
Themes are not serializable, so I can't use getInitialProps, but I don't want to require them all in bundle, they will bloat it.

So I decided to go with conditional require on server side and with dynamic import on client, but as one would expect got blinking due to first render not having a theme yet.
So it renders properly -> React hydrates synchronously -> renders without theme (view is null in this case, can't render without theme) -> renders with theme.

On any other app, I would just load everything I need before initializing React, how can I do it with next then?

Progressive server side render might be one solution to this.

Link to the example project

import { useEffect, useState } from 'react'

function useMounted() {
  const [mounted, setMounted] = useState(false)
  useEffect(() => setMounted(true), [])
  return mounted
}

export default function HomePage() {
  const isMounted = useMounted()
  return (
    <main>
      <section>
        <h1>This section is server-side rendered.</h1>
      </section>

      {isMounted ? (
        <section>
          <h2>
            This section is <em>only</em> client-side rendered.
          </h2>
        </section>
      ) : (
        <p>Loading ...</>
      )}

      <style jsx>{`
        section {
          align-items: center;
          display: flex;
          height: 50vh;
          justify-content: center;
        }
      `}</style>
    </main>
  )
}

If I understood correctly it's not solving blinking issue and not an option for me, because I have to render server side, too.

So not to ressurect closed issues I have opened a discussion about it, if anyone has any ideas please go here https://github.com/vercel/next.js/discussions/14879. If no solution will be found, I will open a feature request and PR when/if get the time.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

swrdfish picture swrdfish  ·  3Comments

lixiaoyan picture lixiaoyan  ·  3Comments

knipferrc picture knipferrc  ·  3Comments

timneutkens picture timneutkens  ·  3Comments

jesselee34 picture jesselee34  ·  3Comments