{
  "$type": "site.standard.document",
  "bskyPostRef": {
    "cid": "bafyreigawbc632hz5sxbswuyaun3v2vn5nrpxs5a42l2acxl4uik3dbcju",
    "uri": "at://did:plc:25rdn5elo5izoxrmtis34zuk/app.bsky.feed.post/3mp3jlsafx6v2"
  },
  "coverImage": {
    "$type": "blob",
    "ref": {
      "$link": "bafkreiabf2y3goeua5czxsiq52q4vgo2bngfmve6fol2kcwu7u2dbzsxpe"
    },
    "mimeType": "image/webp",
    "size": 72652
  },
  "path": "/childrentime/react-useisomorphiclayouteffect-fix-the-ssr-uselayouteffect-warning-2026-57m0",
  "publishedAt": "2026-06-25T03:22:51.000Z",
  "site": "https://dev.to",
  "tags": [
    "react",
    "javascript",
    "webdev",
    "tutorial",
    "useEffect",
    "useIsomorphicLayoutEffect",
    "Rules of Hooks",
    "useUpdateLayoutEffect",
    "useUpdateEffect",
    "useMount",
    "useEvent",
    "reactuse.com",
    "@reactuses"
  ],
  "textContent": "You added a `useLayoutEffect` to measure a tooltip, shipped it, and the next time your Next.js (or Remix, or Gatsby) dev server rendered a page on the server, the console lit up:\n\n\n\n    Warning: useLayoutEffect does nothing on the server, because its effect cannot\n    be encoded into the server renderer's output format. This will lead to a\n    mismatch between the initial, non-hydrated UI and the intended UI. To avoid\n    this, useLayoutEffect should only be used in components that render exclusively\n    on the client.\n\n\nThe warning is correct, the suggested fix (\"only use it on the client\") is unhelpful, and the obvious workaround — just switch to `useEffect` — quietly reintroduces the visual bug you used `useLayoutEffect` to kill in the first place. `useIsomorphicLayoutEffect` is the small hook that resolves the standoff. This post explains exactly why the warning happens, why the two naive fixes are both wrong, and what the one-line hook actually does.\n\n##  Why useLayoutEffect Exists At All\n\nReact gives you two effect hooks that look nearly identical:\n\n  * useEffect runs **after** the browser has painted. Its callback is queued and fires asynchronously once the frame is on screen.\n  * `useLayoutEffect` runs **before** the browser paints, synchronously, right after React has mutated the DOM but before the user sees anything.\n\n\n\nThat timing difference is the whole point. If you need to read layout — `getBoundingClientRect`, `scrollHeight`, the measured width of a node — and then write a style based on it, you have to do it _before_ paint. Otherwise the user sees one frame of the wrong layout, then a flicker as your `useEffect` corrects it. The canonical example is a tooltip that has to position itself relative to its own measured size:\n\n\n\n    function Tooltip({ targetRect, children }) {\n      const ref = useRef<HTMLDivElement>(null);\n      const [pos, setPos] = useState({ top: 0, left: 0 });\n\n      useLayoutEffect(() => {\n        const { height, width } = ref.current!.getBoundingClientRect();\n        // place the tooltip above the target, centered\n        setPos({\n          top: targetRect.top - height - 8,\n          left: targetRect.left + targetRect.width / 2 - width / 2,\n        });\n      }, [targetRect]);\n\n      return <div ref={ref} style={{ position: 'fixed', ...pos }}>{children}</div>;\n    }\n\n\nWith `useLayoutEffect`, React measures and repositions in the same synchronous pass, so the tooltip is only ever painted in the right spot. Swap in `useEffect` and the tooltip flashes at `{ top: 0, left: 0 }` for one frame before jumping into place. On a fast machine you might not notice; on a throttled phone you absolutely will.\n\n##  Why the Server Hates It\n\nServer-side rendering produces an HTML string. There is no browser, no DOM, no layout phase, and — critically — nothing ever _paints_. The entire reason `useLayoutEffect` exists is to run synchronously before a paint that, on the server, never comes.\n\nSo React makes a deliberate choice: **`useLayoutEffect` callbacks do not run during server rendering at all.** They can't be meaningfully serialized into the HTML, and running them would do nothing useful. React knows this is a footgun — your component's server output won't reflect whatever the layout effect would have computed — so it emits that warning to tell you the server HTML and the intended client UI may not match.\n\nThe warning is not a bug in your code. It is React pointing out that you have a hook whose _only job_ is impossible to do on the server.\n\n##  Why You Can't Just Use useEffect\n\nThe first instinct is to silence the warning by switching to `useEffect`, which React is perfectly happy to run on the server (it just defers the callback). The warning disappears. The flicker comes back.\n\nRemember the timing: `useEffect` fires _after_ paint. So on the client, after hydration, your measure-then-reposition logic now runs one frame late. The user sees the un-positioned state first, then the correction. You traded a console warning for a visible visual glitch — a strictly worse outcome, because at least the warning was invisible to users.\n\nThe second instinct — render the component only on the client (`typeof window !== 'undefined'` guards, dynamic imports with `ssr: false`, mounting flags) — works but throws away server rendering for that whole subtree. You lose the SSR HTML, the content is invisible to crawlers until hydration, and you've added a layout-shift on first load. That's a sledgehammer for a hook-selection problem.\n\n##  The Actual Fix: Branch on Environment\n\nThe realization is simple: you want `useLayoutEffect`'s pre-paint timing **in the browser** , and you want `useEffect`'s \"quietly do nothing useful, no warning\" behavior **on the server**. Those are two different hooks, and which one is correct depends entirely on where the code is running.\n\nSo pick at module-load time based on whether you're in a browser:\n\n\n\n    import { useEffect, useLayoutEffect } from 'react';\n\n    const isBrowser = typeof window !== 'undefined';\n\n    export const useIsomorphicLayoutEffect = isBrowser ? useLayoutEffect : useEffect;\n\n\nThat is the entire hook. In the browser it _is_ `useLayoutEffect` — identical pre-paint, synchronous timing, identical signature. On the server it _is_ `useEffect`, which React never warns about and which never runs a useless layout pass. \"Isomorphic\" is the old term for code that runs the same way on server and client; the hook picks the right same-meaning effect for each environment.\n\nReactUse ships exactly this as useIsomorphicLayoutEffect, so you don't copy-paste the snippet into every project:\n\n\n\n    import { useIsomorphicLayoutEffect } from '@reactuses/core';\n\n    function Tooltip({ targetRect, children }) {\n      const ref = useRef<HTMLDivElement>(null);\n      const [pos, setPos] = useState({ top: 0, left: 0 });\n\n      // Same code as before — but no SSR warning, and no client flicker.\n      useIsomorphicLayoutEffect(() => {\n        const { height, width } = ref.current!.getBoundingClientRect();\n        setPos({\n          top: targetRect.top - height - 8,\n          left: targetRect.left + targetRect.width / 2 - width / 2,\n        });\n      }, [targetRect]);\n\n      return <div ref={ref} style={{ position: 'fixed', ...pos }}>{children}</div>;\n    }\n\n\nIt's a drop-in replacement for `useLayoutEffect`: same callback, same optional dependency array, same cleanup function. The only thing that changes is that the warning goes away and your client behavior stays identical.\n\n###  One subtlety: why the branch lives outside render\n\nNotice the `isBrowser ? useLayoutEffect : useEffect` runs _once_ , at module evaluation, not inside the component. That's deliberate. The Rules of Hooks require that you call the same hooks in the same order on every render. If you wrote `if (isBrowser) useLayoutEffect(...) else useEffect(...)` _inside_ the component, you'd technically be calling different hooks on server vs client — and worse, the linter would (rightly) complain about a conditional hook call.\n\nBy resolving the choice to a single stable function reference at module load, the component just calls `useIsomorphicLayoutEffect(...)` unconditionally. `isBrowser` never changes within a process, so the selected hook is constant for the lifetime of the bundle. Hook order stays stable; the lint rule stays happy.\n\n##  When To Reach For It (And When Not)\n\nUse `useIsomorphicLayoutEffect` when **all** of these are true:\n\n  * You need layout-phase timing — you're measuring or mutating the DOM and the result must be on screen in the _first_ painted frame (tooltips, popovers, autosizing textareas, scroll restoration, focus management, anything where a one-frame flash is visible).\n  * The component is server-rendered (Next.js, Remix, Astro islands, Gatsby, TanStack Start — anything that calls `renderToString`/`renderToPipeableStream`).\n  * You want the SSR warning gone without disabling SSR for the subtree.\n\n\n\nDo **not** reach for it as a blanket replacement for `useEffect`. If your effect doesn't touch layout — fetching data, subscribing to an event, syncing to `localStorage`, logging — plain `useEffect` is correct and you want its post-paint, non-blocking timing. `useLayoutEffect` (and therefore the isomorphic version) runs synchronously and _blocks paint_ ; overusing it makes your app feel janky for no benefit. The rule of thumb hasn't changed: reach for layout effects only when you'd otherwise see a flicker.\n\nAnd if a component is genuinely client-only — it imports `window` at the top level, or wraps a browser-only library — rendering it client-side (`dynamic(() => ..., { ssr: false })`) is still the right tool. `useIsomorphicLayoutEffect` is for components that _do_ render on the server and just have a layout effect inside them.\n\n##  The Layout-Timing Family\n\n`useIsomorphicLayoutEffect` is the base of a small family of effect hooks in ReactUse. Once you understand the SSR-safe layout-effect, the rest fall out naturally:\n\n  * useUpdateLayoutEffect — a layout effect that **skips the first mount** and only runs on updates. Internally it wraps `useLayoutEffect` with a first-mount guard, so it's the layout-phase sibling of `useUpdateEffect`. Handy when the initial DOM is already correct and you only need to react to subsequent prop changes (animating a value _to_ a new position, not _into_ existence). Note that this one uses `useLayoutEffect` directly — combine the pattern with an `isBrowser` branch if you need it SSR-silent.\n  * useUpdateEffect — the same skip-first-render behavior on top of `useEffect`. The everyday \"run this on change but not on mount\" hook.\n  * useMount — runs a callback exactly once after mount. A readable alias for `useEffect(fn, [])` when all you mean is \"on mount\".\n\n\n\nThere's also a quietly important consumer of this hook inside the library itself. useEvent — ReactUse's stable-callback hook, the one that gives you an event handler with a permanent identity but always-fresh closure — uses `useIsomorphicLayoutEffect` to sync the latest function into a ref _before_ paint:\n\n\n\n    const handlerRef = useRef(fn);\n    useIsomorphicLayoutEffect(() => {\n      handlerRef.current = fn;\n    }, [fn]);\n\n\nWriting the ref in the layout phase guarantees that if any child fires the handler during _its_ layout effect, it already sees the newest version — and doing it isomorphically means `useEvent` itself never trips the SSR warning. It's a good illustration of why a library hook reaches for the isomorphic variant by default: you don't know which environment your consumers run in, so you pick the one that's correct in both.\n\n##  Takeaways\n\n  * The warning \"useLayoutEffect does nothing on the server\" is React telling you a pre-paint hook can't run where there's no paint. It's accurate, not a false alarm.\n  * Switching to `useEffect` silences the warning but reintroduces a one-frame flicker on the client, because `useEffect` runs after paint.\n  * `useIsomorphicLayoutEffect` resolves both: it _is_ `useLayoutEffect` in the browser and `useEffect` on the server, chosen once at module load so hook order stays stable.\n  * Use it for layout measurement/mutation in server-rendered components; keep plain `useEffect` for everything that doesn't touch layout.\n  * ReactUse ships it (and the related `useUpdateLayoutEffect`, `useUpdateEffect`, `useMount`) so you don't reinvent the one-liner — and uses it internally to keep its own hooks SSR-safe.\n\n\n\nBrowse the full set of SSR-safe effect hooks at reactuse.com, and drop `useIsomorphicLayoutEffect` in wherever a `useLayoutEffect` is making your server console nervous.",
  "title": "React useIsomorphicLayoutEffect: Fix the SSR useLayoutEffect Warning (2026)"
}