StrictMode 는 dev 환경에서 effect / 컴포넌트 / state initializer 를 의도적으로 두 번 실행해 cleanup 누락 / 비순수 코드를 노출. production 에선 한 번. "두 번 실행되는 게 비정상이다" 가 아니라 "두 번 실행되어도 같아야 한다" 가 맞음.
📖 핵심 개념
StrictMode 가 두 번 실행하는 것: function component body, useEffect (mount → cleanup → mount), useState/useReducer initializer, useMemo factory.
목적: idempotent + cleanup 검증. 한 번 실행과 두 번 실행 결과가 같으면 향후 React 의 offscreen 렌더 / 재마운트 최적화에 안전.
💻 코드 패턴
// ❌ 두 번 실행 시 사고
useEffect(()=>{socket.connect();// 두 번 호출 → 두 connection
},[]);// ✅ cleanup 으로 idempotent
useEffect(()=>{socket.connect();return()=>socket.disconnect();},[]);// ✅ initializer 도 pure
const[counter]=useState(()=>loadFromStorage());// OK if pure read
const[id]=useState(()=>Math.random());// ❌ 두 번 실행되면 다른 id
const[id]=useState(()=>crypto.randomUUID());// 같은 문제
// → useRef 또는 lazy ref pattern
constidRef=useRef<string>();if(idRef.current===undefined)idRef.current=crypto.randomUUID();
🤔 의사결정 기준
상황
StrictMode-friendly 패턴
WebSocket / EventSource 연결
connect/disconnect cleanup
외부 라이브러리 init (chart, map)
init/destroy cleanup
analytics ping (mount 시 1회)
useEffect 안 쓰고 onClick / 라우터 이벤트로
한 번만 실행되는 unique id
useRef 패턴
비싼 fetch (두 번 호출 부담)
React Query / SWR — 자체 dedup
❌ 안티패턴
StrictMode 끄기: 단기 회피. 사고 잠재. 켜놓고 root cause 수정.
if (didMountRef.current) return; didMountRef.current = true;: hack. 진짜 문제는 cleanup 미흡.
production 에서도 같은 문제 해결되었다고 가정: prod 는 한 번 실행 — dev StrictMode 가 미래의 unmount/remount 시뮬.
useEffect 의 cleanup 안에서 또 mutation: cleanup 도 idempotent.
🤖 LLM 활용 힌트
"StrictMode 가정. effect 가 두 번 실행되어도 안전한가?" 라고 매번 점검.