Medium
이소모픽 레이아웃 이펙트라는건 없다
useIsomorphicLayoutEffect 는 사실 "서버에서 아무것도 안 함"을 우회로 표현한 패턴이다. 경고를 끄는 게 목적이라면 컴포넌트 자체를 클라이언트 전용으로 두는 게 더 정직한 답일 수 있다.
핵심 요약
useLayoutEffect 는 클라이언트에서 DOM 커밋 후 페인트 전에 실행되어 레이아웃 측정/수정에 사용된다. SSR에서는 DOM이 없어 실행 자체가 불가능 → 리액트가 경고를 띄움. useIsomorphicLayoutEffect 류는 "클라이언트면 useLayoutEffect, 아니면 useEffect"로 분기해 경고만 끄는 코드인데, 서버에서 useEffect 도 어차피 실행되지 않으므로 사실상 noop이다. 진짜 문제는 두 가지로 나뉜다: (1) 레이아웃을 "수정"하는 이펙트 → 컴포넌트를 클라이언트 전용(use client/dynamic import)으로 두는 게 맞다. (2) DOM 을 "읽기만" 하는 이펙트 → 서버에서 실행 안 돼도 안전하므로 경고만 무시해도 된다. "이소모픽" 별칭은 이 둘을 구분하지 않고 모두에게 동일한 회피책을 권한다는 점이 함정이다.
useLayoutEffect 는 DOM 커밋 직후, 페인트 직전 에 동기로 실행되는 슬롯이다. 서버에는 DOM도 페인트도 없으므로 자연스럽게 실행되지 않는다. 따라서 "이소모픽"(서버·클라이언트 동일 거동)은 정의상 불가능하며, 흔히 쓰이는 useIsomorphicLayoutEffect 는 경고 회피용 별칭일 뿐이다.
면접에서 "useEffect vs useLayoutEffect" 만 묻는 게 아니라, SSR 환경에서 어떻게 동작하나요? 까지 들어가는 질문이 늘고 있다. 이 글은 "왜 모두가 react-redux 의 임시 패치를 그대로 복사해 표준이 됐는가"를 추적해, 패턴을 외우지 말고 의도를 이해해야 한다는 교훈을 준다.
학습 포인트
면접 답변으로 연결할 학습 포인트입니다.
`useLayoutEffect` 가 정확히 언제 실행되는가
리액트의 라이프사이클을 시간순으로 보면:
- 렌더 함수 실행 → 가상 DOM 생성
- 실제 DOM 커밋
useLayoutEffect실행 (동기)- 브라우저 페인트
useEffect실행 (비동기)
사용자에게 "잘못된 위치의 툴팁"이 한 프레임이라도 보이면 안 되는 경우 useLayoutEffect 가 필요하다.
useLayoutEffect 와 useEffect 를 "성능 차이"로만 이해. 실제 차이는 페인트와의 순서다.
SSR에서 `useLayoutEffect` 가 경고를 띄우는 이유
서버에는 DOM 도 페인트도 없으므로 레이아웃 이펙트는 실행되지 않는다. 그러면 "초기 HTML"과 "하이드레이션 후 의도한 UI" 사이 불일치가 생긴다 — 사용자가 잠깐 잘못된 위치의 툴팁을 본다는 뜻. 리액트의 경고는 "이 컴포넌트는 클라이언트 전용으로 두는 걸 고려하라"는 신호.
경고를 노이즈로 간주하고 끄기. 경고 자체가 "UX 문제 가능성"을 알려주는 디버그 신호다.
`useIsomorphicLayoutEffect` 의 실체
import { useEffect, useLayoutEffect } from 'react';
const isClient = typeof document !== 'undefined';
export default isClient ? useLayoutEffect : useEffect;
서버 분기에서 useEffect 를 쓰지만 서버는 어차피 이펙트를 실행하지 않으므로 사실상 noop 의 별칭이다. 효과는 단지 "경고를 안 띄움".
"서버에서 useEffect 가 동작하니까 안전하다"는 오해.
올바른 분류 — 수정 vs 읽기
| 이펙트 종류 | 서버에서 안전한가 | 권장 해법 |
|---|---|---|
| 레이아웃을 "수정" (위치 계산해 setState) | 아니오 — 잘못된 UI 노출 | 컴포넌트 자체를 클라이언트 전용으로(use client / dynamic with ssr: false) |
| DOM 을 "읽기만" (트리 동기화 등) | 예 — 효과가 누적될 뿐 | 서버에서만 경고 무시(useIsomorphicLayoutEffect 류 사용 가능) |
핵심은 "내 이펙트는 첫 페인트의 정확성에 영향을 주는가" 한 줄.
두 종류를 같은 도구로 처리. 수정형은 사용자에게 보이는 깨진 UI 를 만든다.
복사·붙여넣기로 굳어진 "표준"의 위험
react-redux 가 자기 사용 사례에서 "경고 끄기"용으로 만든 코드를 다른 라이브러리들이 그대로 복사 → Medium 글에 정리되어 검색 1순위 → 더 많은 사람이 또 복사. 이렇게 임의 결정이 사실상 표준이 된 사례다. 라이브러리 코드를 읽을 때도 "왜 이렇게 했는가"의 맥락이 더 중요하다는 교훈.
유명 라이브러리의 패턴이라는 이유로 무비판 수용. 원래 결정의 맥락을 따라가 보면 자기 코드엔 안 맞는 경우가 많다.
읽는 순서
- 1이론
리액트 렌더/커밋/페인트 타임라인과, SSR 에서 이펙트가 실행되지 않는 이유를 정리.
- 2구현
Next.js App Router 환경에서 (1) 클라이언트 전용 분리, (2)
useIsomorphicLayoutEffect패치, (3) 그냥useEffect로 다운그레이드 — 세 패턴을 같은 컴포넌트에 적용해 첫 페인트 차이를 캡처해 비교. - 3실무
사내 코드의
useIsomorphicLayoutEffect사용 위치를 "읽기/수정"으로 분류해 부적절하게 쓰인 곳을 PR로 정리. - 4설명
동료에게 "이 경고는 노이즈가 아니라 신호다" 를 한 페이지로 설명할 수 있게 도식화.
면접 연결 질문
[감점 답변] "하나는 동기, 하나는 비동기". [좋은 답변] (1) 시점: useLayoutEffect 는 DOM 커밋 직후·페인트 전 동기, useEffect 는 페인트 후 비동기. (2) 용도: 측정/수정해야 깜빡임 없는 경우 → useLayoutEffect, 그 외 → useEffect. (3) SSR: useLayoutEffect 는 서버에서 실행 안 됨 → 클라이언트 전용으로 두거나 동적 import 로 분리. useEffect 도 서버에서 실행 안 되지만 첫 페인트 정확성에 영향 적음.
[감점 답변] "서버에서도 useLayoutEffect 처럼 동작한다". [좋은 답변] 서버에서도 클라이언트에서도 거동을 같게 만드는 게 아니다. 단지 서버 분기에서 useEffect 를 쓰는 모양만 바꿔 리액트의 경고를 우회하는 것. 서버는 어떤 이펙트도 실행 안 한다. 따라서 "레이아웃을 수정해야 하는 컴포넌트" 라면 클라이언트 전용으로 두는 게 더 정직하다.
[감점 답변] "useEffect 로 바꾼다". [좋은 답변] (1) 읽기만 하는 이펙트(DOM 측정·내부 트리 동기화)면 그대로 두고 경고만 무시 가능. (2) 레이아웃을 setState 하는 이펙트면 "use client" 컴포넌트로 분리하거나 dynamic(() => import('X'), { ssr: false }). (3) 깜빡임이 허용되면 useEffect 로 다운그레이드 가능 — 단, 첫 페인트 위치가 잘못 보이는 트레이드오프 인지.
자기 점검
"두 환경에서 같은 코드가 돈다"고 오해. 서버에서는 어떤 이펙트도 실행되지 않는다.
그냥 useEffect 로 바꾸면 된다는 답. 첫 페인트에 잘못된 위치가 노출된다.