TechFeedTechFeed
Frontend

Too many re-renders, setState, 무한 렌더 | 화면이 멈추고 콘솔만 도나?

Too many re-renders는 훅이 고장난 게 아니라 그리는 도중에 setState가 다시 그려짐을 불러 한도를 넘긴 줄입니다. onClick 호출, 렌더 몸통 갱신, Maximum update depth와 구분. React, Next.js, 한국 1인 개발자 기준. 2026년 9월 React useState 문서.

by

Too many re-renders는 훅이 고장난 게 아니라, 그리는 도중에 setState가 다시 그려짐을 불러 같은 컴포넌트가 한도를 넘긴 줄입니다. 이펙트를 의심하기 전에 렌더 함수 안의 상태 갱신부터 보세요.


버튼을 안 눌렀는데 화면이 하얘지고 콘솔만 같은 문장을 반복하는 일, 넥스트 앱 라우터에서 상태 초깃값을 계산하다 만납니다. 쿼리 훅 경계 에러와 달리 빌드는 되고 그 페이지를 여는 순간에 죽습니다.


Maximum update depth와 한 글자만 다른 줄이니 메시지를 끝까지 복사하세요. 근거는 리액트 useState이펙트가 필요 없을 때에 있습니다.


그리는 도중에 setState가 도는 줄인가

렌더 함수가 끝나기 전에 상태를 바꿔 다시 그리기를 부른 줄입니다. 리액트는 무한 루프를 막으려고 횟수 한도를 두고, 넘으면 Too many re-renders를 던집니다. 이펙트 의존성이 꼬여 나는 Maximum update depth exceeded와 메시지가 다릅니다.


가장 흔한 코드는 조건 없이 렌더 몸통에서 setState를 부르는 경우입니다. if (!ready) setReady(true)처럼 보여도, ready가 다시 false가 되는 계산이면 매 렌더가 상태를 바꿉니다. onClick={setOpen(true)}처럼 핸들러를 넘기는 자리에 호출을 적어 둔 경우도 같습니다. 클릭 전이 아니라 그리는 순간에 실행됩니다.


하이드레이션 미스매치와는 칸이 다릅니다. 서버 HTML과 첫 클라이언트 그림이 다른 줄은 하이드레이션 글입니다. 이쪽은 마운트된 뒤에도 같은 컴포넌트가 자기 상태를 계속 갱신합니다.


메시지 한 줄을 끝까지 | Too many re-renders는 렌더 중 setState, Maximum update depth는 이펙트나 콜백이 상태를 순환 갱신한 줄입니다. 콘솔 첫 문장이 다음 표를 고릅니다.


렌더 중에 setState가 다시 렌더를 불러 Too many re-renders가 나는 개념 이미지
그리는 도중에 상태를 바꾸면 리액트가 한도를 넘기고 멈춘다

두 줄 에러를 표로 가르기

고치는 파일이 달라집니다. 렌더 몸통인지, 이펙트인지부터 나눕니다.


메시지언제 나나먼저 볼 코드
Too many re-renders렌더 함수 안에서 setStateif 옆 setState, onClick={fn()}
Maximum update depth exceeded이펙트·콜백이 상태를 순환useEffect 의존성, 객체 setState
Hydration failed서버와 첫 그림이 다름Date, random, window
missing suspense boundary쿼리 훅에 대체 화면 없음useSearchParams

쿼리 훅 때문에 빌드가 멈추는 줄은 Suspense 경계 글입니다. 이 글은 런타임에 콘솔이 같은 문장을 반복하는 줄만 봅니다.


렌더 중에 바로 실행되는 호출
function Panel({ startOpen }) { const [open, setOpen] = useState(false) // 그릴 때마다 실행됨 if (startOpen) setOpen(true) // 클릭이 아니라 렌더 때 실행됨 return <button onClick={setOpen(true)}>열기</button> }

렌더 안의 갱신을 이벤트나 초깃값으로

고치는 원칙은 그리는 함수를 순수하게 두는 것입니다. 초깃값이 필요하면 useState(() => startOpen)처럼 함수 초깃값으로 한 번만 계산합니다. 클릭 뒤에 바꿀 값은 onClick={() => setOpen(true)}처럼 함수를 넘깁니다.


파생 값은 상태로 복제하지 않습니다. const full = first + last면 매 렌더 계산으로 충분합니다. 그걸 setFull으로 저장하려고 렌더나 이펙트에 넣으면 순환이 시작됩니다. 리액트 문서도 이펙트 없이 계산할 수 있으면 상태로 두지 말라고 안내합니다.


부모에서 받은 props를 상태에 복사하는 패턴도 자주 걸립니다. props가 바뀔 때마다 렌더에서 setValue(props.value)를 호출하면 한도를 넘깁니다. 통제 컴포넌트면 상태를 부모만 들고, 복사본이 필요하면 key로 컴포넌트를 다시 마운트하세요.


초깃값과 클릭 핸들러로 나누기
function Panel({ startOpen }) { const [open, setOpen] = useState(() => startOpen) return ( <button type="button" onClick={() => setOpen(true)}> 열기 </button> ) }
  • [ ] 콘솔 첫 문장이 Too many re-renders인지 확인했다
  • [ ] 렌더 몸통의 setState와 onClick={fn()}을 찾았다
  • [ ] 파생 값을 상태로 복제하지 않게 바꿨다
  • [ ] 초깃값은 useState 함수 형태로 한 번만 넣었다
  • [ ] 이펙트 루프면 Maximum update depth 글로 넘겼다

onClick에 함수 호출을 적지 않고 화살표 함수로 감싸 렌더 중 실행을 막는 이미지
핸들러 자리에 호출을 적으면 클릭 전에 상태가 바뀐다

이펙트 순환은 다른 줄이다

Maximum update depth exceeded는 렌더가 끝난 뒤 이펙트가 상태를 바꾸고, 그 상태가 이펙트를 다시 부르는 순환입니다. 의존성 배열에 매 렌더 새 객체나 인라인 배열을 넣으면 조건이 항상 바뀐 것으로 보입니다.


고치는 법은 이펙트가 정말 필요한지부터 묻는 것입니다. 서버에서 이미 받은 값을 화면에 그리는 일은 이펙트가 아닙니다. 동기화해야 하는 외부 시스템만 이펙트에 남깁니다. 객체 의존성은 필드를 원시 값으로 쪼개거나, 이전 값과 같을 때 setState를 건너뛰세요.


넥스트에서 클라이언트 컴포넌트만 상태를 가질 수 있습니다. 서버 파일에 훅을 두면 다른 빨간 줄이 납니다. 이 글의 전제는 이미 클라이언트 컴포넌트인데 그 안에서 상태가 순환하는 경우입니다.


setState를 조건으로 감싸도 순환할 수 있다 | 조건이 매 렌더 true면 한 줄짜리 if도 무한입니다. 다음 렌더에서 false가 되는 조건인지 숫자로 확인하세요.


실전에서 찾는 순서

스택에서 내 컴포넌트 이름을 찾고, 그 파일 렌더 몸통의 setState를 검색합니다. 이벤트 핸들러 괄호, props 복사, 파생 값 저장 순으로 지웁니다. 그래도 Maximum update depth면 이펙트 의존성을 봅니다.


개발 모드에서만 두 번 그리는 리액트 19 검사는 이 한도를 앞당길 수 있습니다. 원인 코드가 있는 한 프로덕션에서도 죽습니다. 검사를 끄지 말고 순수 렌더로 고치세요.


컴포넌트 스택에서 렌더 중 setState 위치를 찾아 초깃값으로 옮기는 흐름 이미지
스택의 내 파일부터 열고 렌더 몸통의 setState를 검색한다

참고 자료


내부 연계: 하이드레이션 미스매치, useSearchParams 경계, 폼 상태 훅


인용한 동작은 2026년 9월 공개 문서 기준입니다.


자주 묻는 질문

버튼을 안 눌렀는데 화면이 멈춥니다.

onClick에 함수가 아니라 호출을 적은 경우가 많습니다. onClick={setOpen(true)}는 그리는 순간에 실행됩니다. 화살표 함수로 감싸세요.


if로 감쌌는데도 같은 줄이 납니다.

조건이 다음 렌더에서 false가 되지 않으면 매 그림이 상태를 바꿉니다. 초깃값으로 옮기거나, 한 번만 true가 되는 값인지 숫자로 확인하세요.


Maximum update depth와 같은가요?

아닙니다. 그쪽은 이펙트나 콜백이 끝난 뒤에 상태를 순환 갱신한 줄입니다. Too many re-renders는 그리는 도중의 setState입니다.


props를 상태에 복사하면 안 되나요?

렌더마다 setState로 복사하면 한도를 넘깁니다. 통제 컴포넌트면 부모 값만 쓰고, 마운트 초깃값만 필요하면 useState(() => props.x)를 쓰세요.


하이드레이션 에러와 같이 뜹니다.

서버와 첫 그림이 다른 줄과, 그 다음 상태가 순환하는 줄은 별개입니다. Date나 window를 렌더에서 읽는지 먼저 보고, 그다음 setState 위치를 보세요.


개발 모드에서만 두 번 그려서 그런가요?

검사는 한도를 앞당길 수 있습니다. 렌더가 순수하지 않으면 프로덕션에서도 죽습니다. Strict Mode를 끄지 말고 호출을 이벤트 뒤로 옮기세요.


Too many re-renders는 그리는 도중에 상태를 바꿔 다시 그리기를 부른 줄입니다. 핸들러 괄호와 렌더 몸통의 setState를 먼저 지우고, 파생 값은 계산만 하세요. 관련 글: 하이드레이션, Suspense 경계, 폼 상태 훅.


Too many re-renderssetState무한 렌더useState리액트넥스트프론트엔드이펙트렌더개발자

함께 보면 좋은 문제 해결

EXPLORE / Frontend

이어서 읽어보기

전체 토픽 둘러보기