한 1년만의 포스팅이다.
게으름 + 블로그 흥미 떨어짐 + 무기력함 + 이직 후 모르는 도메인 흡수하느라 정신 없음의 결과물.
최근이라고 쓰려고 했는데 이직한지도 1년 반이라는 시간이 지났다. 2026년 1월에 작업했던 내용에 대한 후기와 최근 작업 플로우 변경에 대해 기록하려고 간만에 벨로그를 켰다.(클로드한테 나중에 이것도 긁어다가 쓰게하려고 기록해봄, 모든것은 AI를 위한 목적으로 바뀌어버린 일상)
2025년 하반기에 담당하고 있는 블록체인 거래내역/지갑/컨트랙트 정보 표시 (Explorer라고들 표현함) 및 데이터 분석 사이트 대규모 마이그레이션을 진행한 후, 2026년에는 이어서 최적화 작업을 진행했다.
대규모 마이그레이션은 codex + cursor와 함께했고, 이 내용은 추후 적을만한 내용이 있으면 적어보고자 한다. 근데 이때는 토큰 부족으로 좀 비효율적으로 진행한 부분이 많아서 고생한 부분중에 공유할 만한 부분이 있다면 다시 돌아오겠다. (이러고 안올듯)
일단 왜 대규모 마이그레이션과 최적화 작업을 하게되었나 ?
- blockscout 오픈소스 기반이라, 사실 커스텀 한 부분 빼고는 크게 기능적으로 건드릴만한 프로젝트는 아니다. 그리고 나는 처음부터 해당 오픈소스를 가지고 커스텀에 기여한 일원이 아니고 입사 이후 처음 전담했기 때문에 건드리기가 좀 애매했다.
- 하지만 주요 버전들 특히 node engine 관련 배포환경이 너무 legacy가 많았고 node 버전이 이제 정식 업데이트가 종료되어가는 시점이었기 때문에 더이상 미룰 수가 없었다
- 그렇게 마이그레이션을 엄청난 삽질을 통해 완료 한 후에. 페이지 스모킹 테스트를 진행하면서 특정 페이지들의 렌더링 속도가 지나치게 느리다는 인상을 지울 수 없었다. blocks 데이터 페이지, tokens 데이터 페이지, transaction 데이터 페이지 등 table 기반 페이지가 그러했다.
- 혹시 마이그레이션 이슈인가 싶어서 기존 버전에서도 Lighthouse를 돌려봤는데, 원래부터 느렸던 이슈였다... 그리고 유독 chrome에서만 발생하고 있었다.
- Network Tab을 통해 API response 도달 대비 렌더링을 비교해봐도 지나치게 느렸다.
초기에 엄청나게 느린 페이지 모습 및 성능

무엇이 원인이었나?
- lighthouse 보고서를 보면, LCP/TBT가 특히 문제인 것이 보이는데 각 수치의 의미는 다음과 같다.
LCP → "사용자가 의미 있는 화면을 보기까지 얼마나 걸리는가"
(what: 메인 테이블이 보이는 시점 / why: 번들, 초기 렌더링 수, 렌더 블로킹)
TBT → "화면이 보인 후 실제로 인터랙션할 수 있기까지 얼마나 막혀 있는가"
(what: Long Task 누적 / why: 동기 연산, 대량 리렌더링, 무거운 컴포넌트 마운트)
- 해당 페이지는
- Websocket을 통해 실시간 데이터를 가져옴
- Table view, layout=auto로 인한 초기 렌더링 연산 증가
- 실시간 블록 업데이트에 따라, 신규 block row가 계속 추가되는 구조(물론 제한은 한 페이지당 75개의 블록을 넘을 수 없고 이후에는 업데이트가 되지 않고 새로고침을 유도하는 UX긴 함)
- Table Row 내부에 실시간 데이터 반영(업데이트 시기 연산), gas used 연산 및 표기, eoa/aa 등의 주소 표기 잘림에 의한 tooltip 및 동적 화면 계산 로직 등 무거운 연산이 많이 포함됨.
- 시도한 것
- 가상화 적용 ❌
- 의도: 현재 페이지 뷰에 노출되어있는 리스트만 렌더링해서 초기 렌더링 비용을 줄여보기 위함이었다.
- 과정&결과: row 하나당 렌더링 비용이 너무 높아서, 전체적인 렌더 비용을 줄였지만 스크롤 할때마다 white space가 보이는 시간이 너무 길었다. 사실 row 자체의 비용을 줄이면 됐겠지만, 굉장히 파일이 많고 복잡한 프로젝트인데다가 UI 컴포넌트 표기 자체를 간소화해야하는 trade-off를 굳이(?) 감당하고 싶지 않아서 가상화 도입 자체를 철회했다.
- table view 뜯어고치기 ⚠️
- 의도: table view 자체가 연산이 많이 들어가기 때문에 이 문제 자체를 없애려는 시도.
- 과정&결과: 최적화를 위해서는 grid/flex view를 table 인 것처럼 구성하는게 이득이라고 한다. 근데 이미 table로 구성되어있는 view를 고쳤다가 side effect 후폭풍 비용을 감당하는게 두려웠기 때문에 layout=auto로 되어있는 부분을 fixed로 고치고 col 너비를 일정 수준으로 정해두어서 연산을 줄이도록 했다. 이 부분은 그래서 근본적인 해결이라기 보다는 최적화 수치를 상승시키는데 기여했다 정도의 성과가 있었다. 미미하게 200ms 정도 LCP를 줄여주었던 것으로 기억.
- 모바일/데스크탑 뷰 동시 렌더링 제거 ❌
- 의도: 모바일/데스크탑 뷰를 모두 렌더링 대상으로 두고 클라이언트 단에서 조건부 렌더링으로 hidden 처리 하고 있던 거라서 초기 렌더링 비용이 상당했다. 모바일은 테이블 뷰가 아니라 카드뷰라서 레이아웃 자체가 아예 다르다. 그래서 이 부분을 제거하면 최적화에 효과적일 것이라 판단함.
- 과정&결과: ssr 단에서 기본을 desktop이라 두고 렌더링을 했더니 모바일 화면 초기 렌더링에 skeleton이 사라지는 점 + 깜빡거리는 현상이 심한 현상이 생겼다. 각 뷰포트 버전의 컴포넌트 렌더링 연산도가 높아서 깜빡거리는 현상은 css로 아무리 조절해도 생겨버렸다. 본질적으로 컴포넌트 연산도를 줄여야만 했는데 현실적으로 불가능해 보여서 다시 롤백엔딩을 맞이했다.
(샤갈)
- PR(Progressive Rendering) 도입 ✅
- 의도: 가상화 롤백하고 생각해봤는데, 결국 핵심은 초기 렌더링에 있기 때문에 초기에만 렌더링되는 row를 줄이면 될 것이라고 판단했다.
- 과정&결과: 이미 50개-최대 75페이지(첫 페이지에서만 최신 block row 업데이트가 동작한다) pagination과 제한은 되어있는 상태였는데 실질적으로 화면에 최대 보여지는 row는 10개 남짓이기 때문에 초기 렌더 개수를 15개로 줄이면 되겠는데 싶어서 시도해봤다. 사실상 가상화 시스템의 아이디어를 적절히 차용한 것이다. 결과는 효과적이었다. white-space 현상은 없애면서 렌더링 속도를 높일 수 있었다.
- Websocket response 한 번들로 적용하기
- 의도: 웹소켓 통신은 서버의 응답조건이나 네트워크 영향이 커서 한번에 갑자기 여러개의 blocks event를 받아버리면 여러번 업데이트 로직이 동작하며 렌더링이 버벅일 것이라고 판단했다.
- 과정&결과: 그래서 프론트엔드의 정석적인 문제인 input tag debounce 처리 처럼, 특정 시간 동안 들어온 websocket 응답을 한번의 렌더링으로만 처리하도록 일종의 batch size를 지정했다.(이 표현이 맞나..?) 그리고 websocket 채널의 큰 최적화 효과는 모르겠지만 확실히 갑자기 네트워크 환경이 안좋아졌을때 화면 freeze가 되던 현상이(특히 chrome에서) 사라졌다. 정량적인 수치 분석까지는 못했지만 아마 해당 작업이 영향을 주었을 것이라 생각(희망)한다.
- 기타 번들 사이즈 줄이기
- 이거는 Claude Code 선생님 주력 작업이라서(사실 다른 부분도 아이디어만 내가 제공했지, 작업은 다 Claude Code 선생님이 해주셨다.) 적당히 task 작성시켜서 진행했다.
- lodash-es로 번들 사이즈 줄이고, next.js custom module build로 필요한/자주 사용되는 모듈 단위만 묶어서 번들 사이즈 줄여주는 등..오류 없이 넘 잘해줘서 편했다.
결과적으로 45점에서 91점으로 페이지 인생 역전 시키기에 성공했다. 함께해준 Claude Code에 이 영광을...생산성이 말이 안된다. 이 작업 아마 예전같았으면 한달은 잡고 있었을듯. 테스트까지 2주가 안걸린것 같다. 실질적인 작업은 1주일 이내였다. 내가 직업을 잃을 수도 있지만(ㅠ) 아직까지는 너무 좋다. 눈 건강에 확실히 기여해준다. I LOVE CLAUDE CODE.
