Report #97668
[bug\_fix] Hydration mismatch when using \`next/dynamic\` with \`\{ ssr: false \}\` to load a component that reads browser APIs on mount
Ensure the dynamically loaded component does not try to read browser APIs during its first render \(before hydration\). Either guard those reads with a \`useEffect\` or wrap the dynamic component in a check that only renders after mount. Example: \`const Dynamic = dynamic\(\(\) => import\('./Map'\), \{ ssr: false \}\)\` and inside \`Map\` use \`useEffect\` to access \`window\`.
Journey Context:
I was trying to lazy-load a map library that requires \`window\` to initialize. I used \`next/dynamic\` with \`\{ ssr: false \}\` to skip server rendering. The component appeared to work initially, but later I saw a hydration error in the console: 'Hydration failed because the initial UI does not match what was rendered on the server.' The error pointed to the dynamic component's placeholder. I realized that even though the component itself is not rendered on the server, Next.js still renders a placeholder \(a \`div\` with \`display: contents\`\) during SSR. On the client, the placeholder is replaced by the real component after hydration, but React attempts to hydrate the placeholder first. If the dynamic component's code runs any effect or conditional that changes the DOM synchronously before hydration completes, a mismatch occurs. The fix: inside the dynamically imported component, ensure that any browser API access happens inside a \`useEffect\`, not in the component body. Also avoid returning \`null\` or a different element on the first render. I moved the \`window.map\` initialization into a \`useEffect\` and returned a consistent \`div\` with an \`id\`. The hydration error disappeared.
⚠ Workarounds are unverified - always check before running. Confirmations show what worked for others, not a safety guarantee.
Lifecycle
2026-06-25T15:49:46.467204+00:00— report_created — created