12
votes

Is it possible to enforce an SSR-only mode in Next.js and only partially hydrate the page? Let's say I have this app:

components/dynamic.jsx

export default () => (
  <button onClick={() => console.log("I have been clicked")}>Click me</button>
)

pages/index.jsx

import DynamicComponent from "../components/dynamic.jsx";

export default () => (
  <div>
    <h1>Hello World</h1>
    <p>Lorem ipsum</p>
    <Hydrate>
      <DynamicComponent />
    </Hydrate>
  </div>
);

Now assume we are rendering pages/index.jsx with Next.js so it will be rendered on the server and fully hydrated on the client. For performance reasons (shrink bundle size, reduce execution time) and to have the app play nicer with ads (????) I only want to hydrate the DynamicComponent on the client and at best only load the JavaScript for the DynamicComponent to the client.

Ist that possible with

  • React?
  • Next.js?

Thanks

1
Place a check for process.browser for components you want rendered only on the client. Does that work?likebeats
spring-media is workign on this, apparently: github.com/spring-media/next-super-performancemaosmurf
@maosmurf that is in fact... me :)Lukas
@lukas ;) for now, I'm also happy to have API calls on dynamic pages tamed stackoverflow.com/a/58196267/671373maosmurf
Yeah, also something like this seems to becoming part of React itself addyosmani.com/blog/rehydrationLukas

1 Answers

11
votes

You can do it with a hack:

<>
  <StaticContent>
    <h1>Hello World</h1>
    <p>Lorem ipsum</p>
  </StaticContent>
  <DynamicComponent />
</>

And the StaticContent component:

import { createElement, useRef, useState, useEffect } from 'react'

function useStaticContent() {
  const ref = useRef(null)
  const [render, setRender] = useState(typeof window === 'undefined')

  useEffect(() => {
    // check if the innerHTML is empty as client side navigation
    // need to render the component without server-side backup
    const isEmpty = ref.current.innerHTML === ''
    if (isEmpty) {
      setRender(true)
    }
  }, [])

  return [render, ref]
}

export default function StaticContent({ children, element = 'div', ...props }) {
  const [render, ref] = useStaticContent()

  // if we're in the server or a spa navigation, just render it
  if (render) {
    return createElement(element, {
      ...props,
      children,
    })
  }

  // avoid re-render on the client
  return createElement(element, {
    ...props,
    ref,
    suppressHydrationWarning: true,
    dangerouslySetInnerHTML: { __html: '' },
  })
}
``