스타일 엔진
스타일 엔진이 바뀌면, 같은 CSS도 다시 봐야 합니다
제품의 메모리 문제를 조사하면서 MUI v4의 알려진 누수 이슈도 검토했습니다. 당시 OOM의 원인은 아니었지만, JSS 기반 스타일을 계속 유지하지 않기로 하고 v5 전환을 진행했습니다.
후속 기능 개발 전까지 확보한 시간은 한 달이 채 되지 않았습니다. 최신 버전까지 한 번에 옮기고 검증하기는 어려워, 우선 v5의 스타일 API로 전환한 뒤 후속 이관을 이어가기로 했습니다. v4와 v5를 함께 사용하는 기간을 두고 공통 컴포넌트와 이를 사용하는 화면을 옮겼습니다.
JSS에서 Emotion으로 바뀌자 기존 선택자가 그대로 적용되지 않았습니다. 비활성 상태인 버튼 자체와 버튼 안의 아이콘을 구분해 선택자를 고치고, MUI가 제공하는 클래스 상수도 사용했습니다. 여러 화면에서 공유하는 기본·상태별 스타일은 styled에, 화면마다 필요한 간격과 배치 조정은 sx에 두었습니다. 이때 숫자로 지정한 간격은 테마 값으로 해석될 수 있어, 기존 화면과 같은 크기가 되도록 단위까지 확인했습니다.
- 상태 선택자
&$disabled → &.Mui-disabledJSS의 로컬 규칙 참조를 MUI 상태 클래스로 바꿔 비활성 스타일을 적용했습니다.
- 자식 선택자
& .MuiButton-startIcon선택자 사이에 공백을 넣어 버튼 자체가 아닌 내부 아이콘에 여백을 적용했습니다.
- 간격의 단위
8 → '8px'체크박스 사용처의 간격을
sx로 옮기면서 테마 배수가 아닌 기존 8px을 유지했습니다.
직접 비교하기
버전별 선택자와 간격 단위를 바꿔 이관 전후를 비교해 보세요.
MUI를 직접 실행하는 화면이 아닌 스타일 설명용 예시입니다. 간격은 기본 theme spacing 8px 기준입니다.
같은 비활성 스타일, 달라진 선택자
&$disabled같은 8도, 단위가 다릅니다
sx={{ gap: 8 }}- 검증 방법
- 스타일 컴포넌트는 Storybook에서 확인하고, 나머지 화면과 기능은 수동 QA로 검증했습니다.
라우팅
경로와 검색 조건을 타입으로 연결했습니다
URL의 #을 계기로 기존 HashRouter 구성을 살펴보고, 일반 경로를 사용하는 방식으로 전환을 제안했습니다. 해시 경로는 서버에 전달되지 않는 제약이 있었지만, 실제 장애에 대응한 작업은 아니었습니다.
- 경로 전환
HashRouter → BrowserRouter
라우터뿐 아니라 인증·알림의 이동 처리와 Saga의 경로 조회도 함께 수정했습니다. 인증 함수에는 이동 콜백을 전달해 특정 라우터에 의존하지 않도록 했습니다.
- 타입 연결
TanStack Router 도입
이후 경로와 검색 파라미터를 타입으로 다루기 위해 TanStack Router를 선택했습니다. 타입을 엄격하게 사용하는 팀의 개발 방식에 맞춰, 라우트 정의와 이동 코드를 연결했습니다.
이동할 경로와 경로 파라미터는 정적 타입으로 확인하고, URL에서 들어오는 검색 파라미터는 Zod로 검증했습니다. 데이터·학습 탭을 오갈 때는 선택한 레이블 세트가 유지되도록 검색 조건을 전달했습니다.
화면을 옮겨도, 선택한 레이블 세트는 그대로
레이블 세트를 고른 뒤 데이터와 학습 화면을 오가 보세요.
가상 프로젝트의 URL과 이동 코드를 연결한 설명용 예시입니다. 실제 라우터나 타입 검사를 실행하지 않습니다.
데이터 화면
이 세트의 라벨 데이터를 확인합니다.
같은 선택으로 만드는 이동 코드
navigate({
to: '/main/$mainId/data',
params: { mainId: 'sample' },
search: { labelSetId: 'label-a' },
})- 해시 경로
#/main/sample/data?labelSetId=label-a- 일반 경로
/main/sample/data?labelSetId=label-a
레이블 세트 유지는 두 경로 방식 모두에서 가능합니다. TanStack Router에서는 라우트 정의를 기준으로 이동 경로와 파라미터의 타입을 연결했습니다.
이관 중에는 두 종류의 이탈 조건을 맞췄습니다
편집하지 않았는데도 이탈을 막는 문제가 있었습니다. 앱 내부 이동을 판단하는 shouldBlockFn과 새로고침·탭 닫기를 제어하는 enableBeforeUnload는 별개여서, 한쪽 조건만 바꿔서는 충분하지 않았습니다. 두 조건에 실제 편집 상태를 연결하고, 직접 관리하던 beforeunload 리스너를 제거했습니다.
- 이탈 조건
변경사항 없음 → 바로 이동
변경사항 있음 → 앱 내부 이동은 확인창, 새로고침·탭 닫기는 브라우저 경고
코드 분할은 초기 진입과 이후 이동을 함께 봤습니다
라우트별 코드 분할을 적용하자 로컬 개발 서버의 초기 화면 표시는 빨라졌지만, 다른 페이지로 이동할 때는 지연이 생겼습니다. 자동 코드 분할을 비활성화하고 배포 환경 검증 후 적용하는 방안을 검토했습니다. 운영 환경의 성능 개선으로 확인된 결과는 아닙니다.
문서화
사람에게 묻던 맥락을, 팀의 문서로 남겼습니다
레거시 문서는 흩어져 있었고 오래된 내용도 많았습니다. 팀원 4명 중 팀장을 제외한 3명은 합류한 지 1년 미만이어서, 프로젝트 맥락에 대한 질문이 팀장에게 몰렸습니다. 문서화를 제안하고 집필에 참여했으며, 팀장은 복잡한 버퍼 구조의 설명을 맡았습니다.
- 사람이 읽는 기준 문서
- 데이터 관계와 도입 배경
- AI가 참고할 지침
- 현재 지켜야 할 규칙과 코드 예시
요청 시 실행하는 갱신 스킬
요청할 때, AI 지침을 갱신하는 절차
- 01기준 문서 읽기
- 02대응하는 AI 지침과 차이 확인
- 03핵심 규칙·상세 지침 갱신
- 04요약·파일 경로 점검
CLAUDE.md에는 핵심 규칙을, 별도 문서에는 아키텍처·상태 관리의 상세 지침을 두었습니다.
PR 변경을 검토하는 CI
PR에서, 오래된 문서를 알려주는 절차
- 01PR 변경과 관련 문서 검토
- 02수정할 경로·내용을 댓글로 안내
- 03개발자가 검토·반영
갱신이 필요한 내용을 댓글로 알리도록 CI를 구성했습니다. 실제 문서 수정은 개발자가 맡습니다.
- 확인한 범위
- 문서와 AI 지침은 동료들이 실제 개발에서 참고하고 있습니다.