Hydration Proof

Search documentation

Find a page or section

next/dynamic with ssr: false

Skip the server render for one component, and load its code in the browser.

The next/dynamic ssr: false option is for a component that cannot run on the server. The server shows a fallback. The browser loads and displays the component after hydration. In the App Router, put the dynamic() call in a Client Component.

How next/dynamic ssr: false works

Dynamic imports do not automatically skip server rendering. By default, Next.js still prerenders a dynamically imported Client Component. Add ssr: false to skip that render (Next.js lazy loading docs):

app/stores/store-finder.tsx
"use client";
 
import dynamic from "next/dynamic";
 
// The map library reads window when it is imported.
const StoreMap = dynamic(() => import("./store-map"), {
  ssr: false,
  loading: () => <div className="h-96 rounded bg-muted" />,
});
 
export function StoreFinder() {
  return <StoreMap />;
}

On page load:

  1. The server renders the loading fallback. It does not import the map module.
  2. The browser renders the same fallback during hydration.
  3. Once the map loads, the browser replaces the fallback with the map.

This also works for libraries that read window or document as soon as they are imported. A ClientOnly wrapper hides a component's output on the server, but it does not stop its module from being imported there.

Next.js dynamic import (ssr: false) in the App Router

In the App Router, you cannot call dynamic() with ssr: false from a Server Component. Next.js reports:

`ssr: false` is not allowed with `next/dynamic` in Server Components. Please move it into a Client Component.

Keep the page as a Server Component. Move the dynamic import into a child Client Component, then render that child from the page:

app/stores/page.tsx
// A Server Component: no "use client", no ssr: false here.
import { StoreFinder } from "./store-finder";
 
export default function Page() {
  return (
    <main>
      <h1>Find a store</h1>
      <StoreFinder />
    </main>
  );
}

Here, store-finder.tsx is the Client Component that holds the dynamic() call.

In the Pages Router, you can use dynamic(() => import("…"), { ssr: false }) in a page or component.

When to use ssr: false

Use ssr: false for components that need the browser before they can render:

  • A map, chart, or editor library that reads window, document, or navigator on import.
  • A widget that has no useful server-rendered version.
  • A large browser-only component that can load separately from the initial page.

Do not use it just to hide a hydration error. If a date, price, or name can be rendered consistently, keep it in the server HTML. That way, people and crawlers can see the content before JavaScript loads. See the causes guides for ways to fix mismatches at their source.

ssr: false vs a ClientOnly component

next/dynamic with ssr: falseClientOnly wrapper
Server HTMLThe loading fallbackThe fallback prop
Module on the serverNot runImported and run, not rendered
Code splittingA separate chunkBundled with the page
Where it can be usedClient Components (App Router), any file (Pages Router)Anywhere, including Server Components as a parent

Check the result

Run the check after adding the dynamic import:

npx hydration-proof test

Check the page visually too. If the fallback and loaded component have different sizes, the page may shift even though hydration succeeds. If you still get a finding, check nearby components for browser-only values used during render.