# next/dynamic with ssr: false

> How next/dynamic ssr: false skips prerendering a component, why the App Router only allows it in Client Components, and when a ClientOnly wrapper is better.

Source: https://hydration.jscrate.dev/docs/guides/next-dynamic-ssr-false
Last updated: 2026-09-18

With `next/dynamic`, `ssr: false` skips prerendering one component: the server sends its `loading` fallback, and the browser loads the component's code and renders it after hydration. Both renders React compares show the fallback, so the component cannot cause a mismatch. In the App Router, the call must be inside a Client Component.

## How next/dynamic ssr: false works

`next/dynamic` is a composite of `React.lazy()` and Suspense, and it "behaves the same way in the `app` and `pages` directories" ([Next.js lazy loading docs](https://nextjs.org/docs/app/guides/lazy-loading)). By default, a dynamically imported Client Component is still prerendered. With `ssr: false`, it is not:

```tsx title="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 />;
}
```

What happens on a page load:

1. **The server** renders `StoreFinder` and puts the `loading` fallback where the map goes. The map's module does not run on the server.
2. **Hydration** renders the fallback in the same place, so it matches.
3. **The browser** loads the map's chunk and replaces the fallback with the map.

Because the module does not run on the server, this is the fix for libraries that touch `window` or `document` at import time, which a plain [client-only component](https://hydration.jscrate.dev/docs/guides/client-only-component) cannot help with.

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

Since Next.js 15, `ssr: false` is not allowed in Server Components. Calling it in `app/page.tsx` without `"use client"` fails with:

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

Move the call into a Client Component and render that from the page:

```tsx title="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>
  );
}
```

`store-finder.tsx` above is the Client Component that holds the `dynamic()` call. The Next.js docs add that `ssr: false` only works for Client Components and that the client code-splitting works properly only there.

In the Pages Router there are no Server Components, so `dynamic(() => import("…"), { ssr: false })` works in any page or component.

## When to use ssr: false

Use it when a component cannot render on the server at all:

- a library that reads `window`, `document` or `navigator` when it is imported (maps, some chart and editor libraries);
- a widget whose output depends entirely on the browser, where a server version would be wrong anyway;
- a large component you want out of the first bundle, which also should not block the first paint.

Do not use it to silence a mismatch in content that could render on the server. A date, a price or a user's name can almost always be rendered the same way on both sides, and search engines and link previews then get the content. The [causes guides](https://hydration.jscrate.dev/docs/causes) show how.

## ssr: false vs a ClientOnly component

|                      | `next/dynamic` with `ssr: false`                        | `ClientOnly` wrapper                              |
| -------------------- | ------------------------------------------------------- | ------------------------------------------------- |
| Server HTML          | The `loading` fallback                                  | The `fallback` prop                               |
| Module on the server | Not run                                                 | Imported and run, not rendered                    |
| Code splitting       | A separate chunk                                        | Bundled with the page                             |
| Where it can be used | Client Components (App Router), any file (Pages Router) | Anywhere, including Server Components as a parent |

## Check the result

A correct `ssr: false` component produces no hydration finding. What goes wrong is usually around it: a fallback of a different size shifts the layout, or a sibling still reads the browser during render. hydration-proof tests every route, so a component that moved from the server render to the client render is checked on every page that uses it:

```bash
npx hydration-proof test
```

## Related

- [Client-only components](https://hydration.jscrate.dev/docs/guides/client-only-component)
- [window is not defined in Next.js](https://hydration.jscrate.dev/docs/errors/window-is-not-defined)
- [Next.js hydration errors](https://hydration.jscrate.dev/docs/frameworks/nextjs)
- [useEffect and two-pass rendering](https://hydration.jscrate.dev/docs/guides/useeffect-two-pass-rendering)
- [Browser-only APIs during render](https://hydration.jscrate.dev/docs/causes/browser-api)
