pile·
프론트엔드·여기어때 (GC컴퍼니)여기어때 (GC컴퍼니)·

항공 프론트엔드 구축기 (3/10): prefix를 붙이자 tailwind-merge가 조용히 깨졌다

여기어때 항공 프론트엔드 팀이 공용 컴포넌트 라이브러리 배포 시 스타일 격리를 위해 Tailwind prefix yf-를 도입했다가 tailwind-merge가 충돌 감지를 못 하는 문제를 발견하고, 2세대에 걸쳐 해결한 과정을 다룬다. 최종적으로 twMerge의 classGroups와 conflictingClassGroups에 yf- 접두어 유틸리티를 직접 등록하는 방식을 선택했다.

핵심 포인트
  • twMerge는 bg-로 시작하는 클래스를 background 그룹으로 인식하는데, yf-bg-는 bg-로 시작하지 않아 알 수 없는 문자열로 취급하며 두 클래스 모두 살아남는다. 빌드 에러도 타입 에러도 없이 조용히 깨진다.
  • 1세대 해법(prefix 제거 → twMerge → 재부착)은 커스텀 유틸리티(자체 타이포 클래스 등)를 twMerge가 인식하지 못해 문제의 절반만 해결했다.
  • 2세대 해법은 twMerge에 yf- 접두어 유틸리티를 classGroups와 conflictingClassGroups에 직접 등록하는 방식("속이지 않고 가르친다")으로 근본 해결했다.
  • yf-text- 계열은 크기·정렬·오버플로·데코레이션·색상 5개 그룹으로 분리해야 하며, catch-all인 색상 그룹은 반드시 마지막에 등록해야 한다.
  • padding/margin의 단방향 충돌 관계(p는 px·py·pt... 를 지우지만 px는 p를 지우지 않는다)를 conflictingClassGroups에 명시적으로 반영했다.
상세 정리
  • 배경: 공용 컴포넌트 라이브러리 배포 시 스타일 격리를 위해 Tailwind prefix yf-를 도입했다. 사내 기존 Vue2 라이브러리는 twl- 사용 중이었다.
  • 문제 발견: twMerge('yf-bg-blue-500 yf-bg-red-500')가 두 클래스를 모두 출력한다. bg-로 시작하지 않으니 twMerge가 분류를 못 하고, 빌드·타입 에러 없이 조용히 두 스타일이 적용된다.
  • 선택지 검토: prefix 포기(라이브러리 격리 요건 위반), twMerge 포기·인라인 style(근본 해결 아님), CSS-in-JS(Tailwind 자산 포기) 모두 제외하고 twMerge에 prefix 등록을 채택했다.
  • 1세대(prefixTwMerge): prefix 제거 → 일반 twMerge → prefix 재부착. 커스텀 유틸리티(yds6-TypoUi-14 같은 자체 타이포)는 prefix를 떼도 twMerge가 모르므로 문제의 절반만 해결.
  • 2세대 핵심: ALL_UTILITY_KEYS를 순회하며 classGroups에 그룹 이름과 isAny 매처를 등록하고 conflictingClassGroups에 같은 그룹끼리 충돌 관계를 정의했다.
  • yf-text- 충돌: yf-text-14(크기), yf-text-center(정렬), yf-text-ellipsis(오버플로), yf-text-underline(데코레이션), yf-text-content-primary(색상)가 모두 yf-text-로 시작해 5개 그룹으로 분리했다. catch-all 색상 그룹은 반드시 마지막에 등록해야 다른 그룹을 먼저 매칭한다.
  • yf-border- 충돌: yf-border-t(두께)와 yf-border-border-primary(색상)를 분리 등록했다.
  • padding/margin 단방향 관계: p → px·py·pt·pb·pl·pr 지움, px → pl·pr 지움, py → pt·pb 지움. 단 px가 p를 지우면 위아래 패딩이 사라지므로 역방향 충돌은 등록하지 않았다.
  • 커스텀 브레이크포인트: twMerge는 변형자(variant)를 이름이 아니라 "앞에 붙은 수식어 묶음이 같은가"로 판정하므로 mobile:·desktop: 등 커스텀 브레이크포인트는 자동 처리됐다.
  • 사용 인터페이스: 템플릿 태그 tw``로 감싸 tv()와 결합하며, 컴포넌트에서 tw`yf-flex yf-items-center yf-gap-8` 형태로 사용한다.
  • 남은 한계: classGroups를 수동 유지해야 하며, 새 유틸리티 등록을 빠뜨리면 조용히 안 먹는 문제가 재발할 수 있다.
왜 읽나Tailwind prefix 도입 후 tailwind-merge가 작동하지 않는 문제를 겪는 팀에게 classGroups 직접 등록 패턴과 text-·border-·padding/margin의 충돌 분류 전략을 구체적으로 제공한다.
여기어때 (GC컴퍼니)
여기어때 (GC컴퍼니) 블로그
원문은 여기서 이어서 읽을 수 있어요
원문 읽기
읽음 (0)

이 글과 비슷한

  1. 프론트엔드·여기어때 (GC컴퍼니)여기어때 (GC컴퍼니)·

    항공 프론트엔드 구축기 (7/10): 창구를 하나만 두었습니다

    여기어때 항공 서비스 프론트엔드가 웹과 앱 웹뷰 두 환경에서 동일한 함수 호출로 동작하는 앱 브릿지 추상화 레이어를 설계한 과정을 다룬다. iOS·안드로이드 규약 차이와 "웹에 존재하지 않는 브릿지를 어떻게 호출하나"라는 문제를 단일 추상화 층으로 해결한 구현 사례다.

    요약 이어보기
    #react#typescript#webview+2