React로 개발하다 보면, 분명히 useEffect
에 한 번만 API를 넣었는데…
API 요청 로그가 두 번씩 찍히는 현상을 경험하신 적 있으신가요?
저도 Electron + React 기반 사내 프로젝트를 하다가,
“어? 내 코드가 잘못된 건가? 서버가 중복 응답을 주는 건가?” 하고 한참을 헤맸습니다.
결론부터 말씀드리면, 이건 React StrictMode 때문입니다.
제 삽질 경험을 토대로, 왜 이런 일이 일어나는지, 배포에서는 어떤지,
그리고 제가 얻은 교훈까지 정리해보겠습니다.
React의 StrictMode
는 개발 모드에서만 실행되는 검증 도구입니다.
React가 일부러 컴포넌트를 더 빡세게 실행합니다.
StrictMode는 단순히 렌더링만 두 번 하는 게 아니라,
useEffect
같은 부작용(side effect) 코드도 마운트 → 클린업 → 다시 마운트 흐름으로 반복 실행합니다.
즉, 코드가 이렇게 생겼다면:
useEffect(() => {
fetchApi(); // API 호출
}, []);
실행 순서는 이렇습니다:
fetchApi()
실행 → API 요청 1회fetchApi()
실행 → API 요청 1회👉 결과적으로 API 요청이 2번 전송됩니다.
즉, 내 코드에는 문제가 없고, React가 일부러 그렇게 하는 겁니다.
저도 electron-builder로 패키징해서 실행했을 때는,
로그에 요청이 한 번만 찍히는 걸 확인했습니다.
개발할 땐 두 번, 배포하면 한 번 → 이게 정상입니다.
개발 모드에서 로그가 두 번 찍히는 게 불편하다면,
간단히 useRef
를 써서 가드를 걸 수 있습니다.
const hasFetchedRef = useRef(false);
useEffect(() => {
if (hasFetchedRef.current) return;
hasFetchedRef.current = true;
fetchApi();
}, []);
이렇게 하면 개발 모드에서도 API는 한 번만 호출됩니다.
저는 보통, 조회 API는 두 번 호출돼도 상관없으니 그냥 두고,
중복 호출이 문제가 되는 등록/수정/승인 API만 이런 방어 로직을 넣었습니다.
Electron 프로젝트에서는 페이지 이동(라우터 클릭)마다 조회 API가 다시 호출되는데,
여기에 StrictMode까지 더해지니 “응답이 두 개씩 오는 것처럼” 보여서 헷갈리더군요.
실제로 로그는 이렇게 찍힙니다:
[API ▶ getCpnMember 요청] { cpnId: 2, userId: 2 }
[API ◀ getCpnMember 응답] { result: { pscd: 'OK', data: [], pcsRsltMsg: '응답을 보냈습니다.' } }
[API ◀ getCpnMember 응답] { result: { pscd: 'OK', data: [], pcsRsltMsg: '응답을 보냈습니다.' } }
처음엔 “응답이 두 번 오네?”라고 생각했지만, 사실은 요청을 두 번 보낸 거였어요.
이것도 StrictMode의 영향으로, 배포 모드에선 정상적으로 한 번만 호출됩니다.
다만 페이지 이동 시마다 조회 API가 자동으로 불리는 구조라면,
캐시나 상태 관리 도구를 붙여서 불필요한 호출을 줄이는 게 좋습니다.
이런 문제를 더 깔끔하게 해결하고 싶다면,
React Query나 SWR 같은 데이터 관리 라이브러리를 추천드립니다.
저는 단순 조회 API는 React Query로 관리하고,
트랜잭션성(등록/삭제/승인) API만 직접 호출하도록 나누니 훨씬 편했습니다.
React Query와 SWR은 단순히 API 호출 라이브러리가 아니라, 데이터 캐싱 계층이에요.
이 캐싱 계층이 있으면 “같은 키(key)”로 요청이 들어올 때 다음과 같은 일이 일어납니다:
refetch()
같은 명령을 내리면 그때만 새로 요청.isLoading
), 성공(data
), 에러(error
) 상태를 자동으로 관리해 줘서, 개발자가 직접 loading
state 만들고, try/catch
돌릴 필요가 없어져요.// React Query 예시
const { data, isLoading, error } = useQuery(
['member', cpnId, userId], // 키
() => getCpnMember({ cpnId, userId }) // API 호출
);
위 코드를 StrictMode 환경에서 두 번 실행한다고 가정해봅시다:
['member', 2, 2]
)로 관리 → 실제 네트워크는 1번만 발생 두 번째 호출은 첫 번째 호출의 캐시/Promise를 재활용합니다.SWR도 원리는 비슷한데, 차이점은 “자동 새로고침”과 “Stale-While-Revalidate” 전략이 강점이에요.
개발 모드에서 StrictMode 때문에 네트워크 요청이 2번씩 찍히는 상황에서도:
게다가 캐시·상태 관리까지 자동이니,
“개발 모드라 로그가 번잡해도 실제 서버 트래픽은 안정적으로 1회”라는 안심을 줍니다.
👉 React Query / SWR은 StrictMode 환경에서 API 중복 호출을 막아주는 안전장치 + 상태 관리 자동화 기능을 제공하기 때문에, “로그가 두 번 찍히는 게 헷갈려요”라는 문제와 “불필요한 서버 트래픽” 문제를 동시에 해결해줍니다.
useRef
가드로 중복 호출을 막을 수 있다.저는 이 삽질을 통해, 이제는 API 로그가 두 번 찍혀도 당황하지 않습니다.
“아, StrictMode 때문이지~” 하고 바로 넘길 수 있게 되었죠. 😎
혹시 여러분도 비슷한 경험 있으신가요?
만약 API 로그가 두 번 찍힌다면, 이제 안심하셔도 됩니다.
여러분의 코드가 잘못된 게 아니라, React가 일부러 그런 거니까요!