문제: 차트가 무한히 커진다
- 화면 — LCA 결과 요약
- 증상 — 차트가 계속 커지며 레이아웃이 깨지고 깜빡거림
- 특징 — 멈추지 않는다. 가만히 둬도 계속 커진다
폭을 재봤더니 약 1,200만 px까지 갔다. 매 사이클 약 3.1배씩 커지고 있었다.
원인: CSS 한 줄이 만든 피드백 루프
탭 패널의 컨테이너가 이랬다.
display: grid;
grid-template-rows: ...;
/* grid-template-columns 없음 */
grid-template-columns를 지정하지 않으면 암묵적 컬럼이 생기고, 그 폭은 auto(= max-content) 가 된다. 즉 “안에 든 내용만큼 넓어져라” 는 뜻이다.
여기에 ECharts가 width: 'auto'로 들어가 있었다. 그러면 이런 순환이 생긴다.
- 차트가 컨테이너 폭을 잰다
- 그린 SVG의 고유 너비가 생긴다
- 컬럼이
max-content라 그 너비만큼 컬럼 트랙이 밀린다 - 컨테이너가 넓어진다
- ECharts가 넓어진 컨테이너를 재측정한다
- 1번으로 돌아간다
측정 결과가 측정 대상을 바꾸는 구조다. 브라우저와 차트 라이브러리 각각은 정상 동작하는데, 둘을 연결하니 발산한다.
수정은 한 줄이다.
grid-template-columns: minmax(0, 1fr);
1fr만 쓰면 안 된다. grid 아이템의 기본 min-width가 auto 라 내용이 크면 트랙이 밀려나기 때문이다. minmax(0, 1fr)로 최솟값을 0으로 못 박아야 “부모가 준 폭 안에서만 살아라”가 된다.
적용하니 1324px에서 안정화됐다.
같은 결함군을 전수 점검
한 곳을 고치고 끝내지 않고, “컨테이너 width 제약 누락” 이라는 결함군으로 보고 정적 분석으로 훑었다. 7곳이 더 나왔다.
| 대상 | 조치 |
|---|---|
| 탭 패널 2곳 |
gridTemplateColumns 추가 |
| grid 컬럼 미지정 2곳 | minmax(0, 1fr) |
| flex-column 래퍼 |
min-width: 0 추가 |
| ECharts 래퍼 4곳 |
width: 100% 명시 |
flex 컨테이너에 min-width: 0을 넣는 것도 같은 이유다. flex 아이템의 기본 min-width도 auto 라, 내용이 크면 부모 밖으로 밀고 나간다. grid의 minmax(0, ...)와 정확히 같은 문제의 flex 버전이다.
정직하게 남긴 한계도 있다.
eco-view GHG 페이지는 테스트 계정에 데이터가 없어 차트 라이브 재현은 불가했으나 동일 결함군에 표준 패턴을 적용.
라이브로 재현·수정 확인한 곳은 한 곳이고, 나머지는 같은 패턴이라 함께 고친 것이다. 이 둘을 구분해 적어두지 않으면 나중에 “다 검증했다”로 잘못 읽힌다.
같은 함정, 다른 얼굴: ResizeObserver
며칠 뒤 다른 화면에서 같은 성질의 버그를 만났다.
- 화면 — STEP2 서브탭 바
- 증상 — 깜빡임
- 구조 —
ResizeObserver가 폭을 재서 좁으면 접고(isNarrow) 넓으면 펼친다
문제는 무엇을 쟀는가였다. 관찰 대상이 서브탭의 “내용 컨테이너” 였다.
- 펼친 상태 — 탭이 다 보여 폭이 넓다 → 임계값 초과 → ‘접음’
- 접은 상태 — 드롭다운만 남아 폭이 줄어든다 → 임계값 미만 → ‘펼침’
- 폭이 임계값 근처면 — 무한 진동
이것도 앞의 차트와 정확히 같은 구조다. 관측 결과가 관측 대상의 크기를 바꾼다.
추적이 까다로웠던 이유가 하나 있다.
redux 액션 없이 순수 setState 루프, 서브탭 바 DOM 이 초당 수백 노드 재생성
상태 관리 라이브러리를 거치지 않는 로컬 setState 루프라, Redux DevTools의 액션 로그에 아무것도 안 남는다. 화면은 미친 듯이 깜빡이는데 개발도구는 조용하다.
수정은 측정 대상을 바꾼 것이다.
- 이전 — 접힘/펼침에 따라 폭이 변하는 내용 컨테이너
- 이후 — 접힘/펼침에 흔들리지 않는 외부 래퍼(부모 nav의 가용 폭)
규칙으로 정리하면
두 사건에서 같은 규칙이 나온다.
측정값으로 무언가를 바꿀 거라면, 그 변화에 영향받지 않는 것을 재라.
- 차트 — 부모가 준 폭을 재야 한다. 자기가 그린 결과가 반영된 폭을 재면 안 된다
- 서브탭 — 접힘 여부와 무관한 바깥 폭을 재야 한다. 접히면 변하는 안쪽을 재면 안 된다
CSS에서는 이게 “내용 기반 크기(auto / max-content)를 피드백 경로에 두지 말라” 로 나타난다.
| 상황 | 위험한 값 | 안전한 값 |
|---|---|---|
| grid 트랙 |
auto (암묵 컬럼 포함) |
minmax(0, 1fr) |
| grid/flex 아이템 |
min-width: auto (기본값) |
min-width: 0 |
| 차트 크기 | width: 'auto' |
width: 100% |
auto는 “알아서 해줘”라 편해 보이지만, 누가 누구를 기준으로 삼는지가 애매해진다. 그 애매함이 순환을 만든다.
이런 버그의 성질
두 건 모두 공통점이 있었다.
- 에러가 안 난다 — 예외도, 콘솔 경고도 없다. 브라우저 입장에선 시킨 대로 한 것뿐이다
- 개별 코드는 다 정상이다 — CSS도 맞고 차트 설정도 맞다. 연결했을 때만 발산한다
- 개발도구에 안 잡힐 수 있다 — Redux 액션도, 네트워크 요청도 없는 순수 렌더 루프
- 임계값 근처에서만 터진다 — 서브탭 건은 폭이 애매할 때만 진동한다. 그래서 “내 화면에선 되는데요”가 나온다
그래서 증상으로 검색하기가 어렵다. “차트가 커져요”로는 안 나온다. “무엇이 무엇을 재고, 그 결과가 재는 대상을 바꾸는가” 를 손으로 그려봐야 보인다. 실제로 두 건 다 원인을 찾은 방법이 코드 검색이 아니라 순환을 종이에 그려본 것이었다.