⚠
vite-plugin-ssr has been renamed Vike, see migration guide.

Client-only Components

You can render a component only on the client-side by wrapping it with a <ClientOnly> component.

<ClientOnly>
  <SomeComponent />
</ClientOnly>

Alternatively, you can disable SSR and render the page as SPA instead (while rendering other pages with SSR). See Guides > Render Modes (SPA, SSR, SSG, HTML-only).

Common use cases:

  • Library components that don't support SSR. A solution is to render/load the component only on the client-side.

    Most component libraries nowadays support SSR but some don't. Some even crash when they're merely loaded on the server-side (for example, when libraries have hard references to browser-only APIs/objects such as window).

  • Performance. By using <ClientOnly> with a dynamic import(), you can defer loading heavy components, such as a complex interactive map. That way, your users can already interact with your page before even the browser starts loading that heavy component.

    Vite automatically code-splits const { SomeComponent } = await import('some-library'). In other words, the code of <SomeComponent /> isn't included in the initial JavaScript client-side bundle: it's loaded only when and if import() is executed.

React

To render and load a component only on the client-side:

// /pages/location/select.page.jsx

import React from 'react'

export function Page() {
  // Users can see and interact with the button "Please select a location"
  // before the browser starts loading the code of the lazy-loaded component.
  return <>
    <button>Please select a location</button>
    <Map />
  </>
}

// <Map> is:
//  - Lazy-loaded
//  - Loaded & rendered only in the browser
function Map() {
  const [Component, setComponent] = React.useState(() => Loading)

  // useEffect() callbacks are only run in the browser, consequently the map component
  // is loaded and rendered only in the browser.
  React.useEffect(() => {
    // @ts-expect-error The type provided by @types/react is wrong
    setComponent(() => React.lazy(() => import('some-heavy-map-component')))
  }, [])

  return (
    <React.Suspense fallback={<Loading />}>
      <Component />
    </React.Suspense>
  )
}

function Loading() {
  return <div>Loading map...</div>
}

The logic can be extracted into a generic <ClientOnly> component:

// ClientOnly.jsx

export { ClientOnly }

import React from 'react'

function ClientOnly(props) {
  const [Component, setComponent] = React.useState(() => props.fallback)

  React.useEffect(() => {
    setComponent(() => React.lazy(props.component))
  }, [])

  return (
    <React.Suspense fallback={props.fallback}>
      <Component />
    </React.Suspense>
  )
}
import { ClientOnly } from './ClientOnly'

function Map() {
  return (
    <ClientOnly
      component={() => import('some-heavy-map-component')}
      fallback={<Loading />}
    />
  )
}
function Loading() {
  return <div>Loading map...</div>
}

See also:

Vue

See #278 and in particular:

Contribution welcome to create an example repository.

See also:

Solid

Contribution welcome to create an example.