skip to Main Content

In recent versions of nextjs, one can use the use client directive to tell nextjs to render the component "in the client", instead of on the server. Digging deeper into this, its a terrible name, because components that use use client still render partially on the server, as explained in the NextJS docs on client components.

I have found cases where components that reference libraries that truly depend on browser apis (canvas, webgl, etc), tend not to work correctly with nextjs. I.e. the boundary between what should happen on the server vs the client is not clear, and the interactivity of third party libraries gets buggy. Mapping libraries are a good example.

Is there a way to truly render everything within a component on the client? I.e. for a given component, do nothing on the server, but rather send code to the client that runs similarly to react-dom‘s render function, for a single component? For example, Docusaurus (a static site generator) has the BrowserOnly component, which tells the build phase to not pre-compile its children, but rather only render it within the client. It works great for components that use libraries with browser-heavy apis.

Does NextJS have any kind of mechanism for this? Or are its client-rendering abilities limited to the use client directive, which is not 100% client-side rendering?

2

Answers


  1. If you are relying on a 3rd party library where the components are using client-only features but are not marked with the 'use client' directive, you can create intermediary components where you can add the correct directive yourself. (Read more)

    'use client'
     
    import { Carousel } from 'acme-carousel'
     
    export default Carousel
    

    This would be the cleanest way since you can still make use of the server-side pre-rendering.

    If you still want to avoid this component entirely on the server-side, then you need to lazy load it on the client-side. (Read more)

    import dynamic from 'next/dynamic'
     
    const DynamicCarousel = dynamic(() => import('acme-carousel', {
      ssr: false,
    }).then((module) => module.Carousel))
     
    export default function Home() {
      return <DynamicCarousel />
    }
    
    Login or Signup to reply.
  2. You could use useState to render the component only on the client, for example:

    export default function NextjsPage() {
        const [hydrated, setHydrated] = React.useState(false)
        React.useEffect(() => {
            setHydrated(true)
        }, [])
    
        return (
            <>
                {hydrated && <MyCanvasComponent />}
            </>
        )
    }
    

    Or if you’re using React 18 or later, you could use useSyncExternalStore instead of having to create a state variable. See this hook as an example (this hook is written for Remix but should work in next.js too):

    https://github.com/sergiodxa/remix-utils/blob/main/src/react/use-hydrated.ts

    BTW, remix-utils also includes a <ClientOnly> component for convenience (in practice I use a mix of useHydrated and ClientOnly depending on whether or not the component I want to render only client-side is at the top level or not). That should also theoretically work in next.js.

    Here’s a little more background in case you’re not already aware: when React renders on the client, it does an initial render pass called "hydration" which re-renders what was already renders on the server and hydrates the components in memory with the state they had on the server. React will give you a warning and you could run into issues if what was rendered on the server doesn’t match the client. That’s why for anything you want to render only on the client, it needs to happen on a second (or later) render pass, after hydration. useEffect() always happens in the browser, which is why that’s one effective solution to this.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search