← 전체 글로 돌아가기

Next.js

React에서 useEffect 배포 문제를 디버깅하기

useEffect는 로컬에서 완벽해도 배포 환경에서 다르게 작동할 수 있다. 환경 차이를 먼저 확인해야 한다.

React의 useEffect는 강력하지만, 부작용(side effect)이 여러 번 실행되거나 예상과 다르게 작동할 수 있다. 특히 로컬 환경과 배포 환경에서 다르게 보일 때가 있다.

프로덕션 빌드로 재현

먼저 프로덕션 빌드 환경에서 문제를 재현해야 한다:

npm run build
npm start  # 또는 프로덕션 환경 서버 실행

Dev 모드에서는 Strict Mode로 인해 effect가 두 번 실행된다. 이것은 의도적이지만, 실제 프로덕션에서는 한 번만 실행된다. 만약 프로덕션에서만 문제가 생기면, 이 동작 차이 때문일 가능성이 높다.

실제 API 응답 확인

로컬에서는 mock API를 사용하지만, 배포 환경에서는 실제 API 응답이 다를 수 있다:

useEffect(() => {
  console.log('데이터 요청 시작');
  fetch(API_ENDPOINT)
    .then(res => res.json())
    .then(data => {
      console.log('받은 데이터:', data);
      setData(data);
    });
}, [API_ENDPOINT]);

브라우저 개발자 도구의 Network 탭에서 실제 API 응답을 본다. 응답 시간이 로컬보다 오래 걸리면, effect가 여러 번 실행되거나 race condition이 발생할 수 있다.

의존성 배열 검토

Effect의 의존성이 정확한지 확인한다:

// 문제: 렌더링할 때마다 실행됨
useEffect(() => {
  fetchData();
});  // 의존성 배열 없음

// 올바름: 한 번만 실행
useEffect(() => {
  fetchData();
}, []);  // 빈 배열

// 조건부 실행
useEffect(() => {
  if (userId) {
    fetchUserData(userId);
  }
}, [userId]);

의존성 배열을 비우면 컴포넌트 마운트 시에만 실행되고, 배열에 값을 넣으면 그 값이 바뀔 때마다 실행된다. 특정 조건에서만 effect를 실행해야 하면, 조건을 effect 내부에 넣는다.

모바일 환경에서의 네트워크 흐름

모바일에서는 네트워크 상황이 예측 불가능하다:

  • 초기 로딩 후 네트워크가 끊길 수 있다.
  • API 응답이 예상보다 오래 걸릴 수 있다.
  • 요청이 여러 번 중복으로 보내질 수 있다.

타임아웃을 설정하고, 이전 요청을 정리하는 cleanup 함수를 항상 포함한다:

useEffect(() => {
  let isMounted = true;
  const controller = new AbortController();

  fetch(API_ENDPOINT, { signal: controller.signal })
    .then(res => res.json())
    .then(data => {
      if (isMounted) setData(data);
    });

  return () => {
    isMounted = false;
    controller.abort();
  };
}, []);

단계별 확인

  1. 증상을 정확히 기록한다. 어떤 UI가 예상과 다른가?
  2. 브라우저 콘솔과 Network 탭에서 요청/응답을 추적한다.
  3. 로컬 개발 환경과 프로덕션 빌드 환경에서의 동작을 비교한다.

마무리

Effect의 렌더링 흐름을 완벽히 이해하면, 배포 후 버그를 크게 줄일 수 있다. 특히 프로덕션 환경에서 실제 데이터로 테스트하는 습관이 중요하다.