소비 포털 Ant Design 전환 인수인계서
문서 목적
이 문서는 UiPlatform에서 수행한 Ant Design 전환 작업을 다음 소비 포털에 반복 적용하기 위한 기준서입니다. 소비 포털은 공통 UI 라이브러리인 portal-foundation을 사용하므로, 개별 앱에서 임시 CSS를 추가하는 방식보다 공통 Shell 계약과 Ant Design Layout 구조를 먼저 확인해야 합니다.
핵심 원칙: 메뉴 이벤트는 명시적으로 연결하고, Shell의 폭·높이·grid 영역은 구조적으로 보장하며, 소비 포털에서는 Foundation 내부 레이아웃을 CSS override로 덮지 않습니다.
현재까지 확정된 변경 사항
| 영역 | 결정 사항 | 적용 위치 |
|---|---|---|
| UI 프레임워크 | Ant Design 6.x 기반으로 전환 | portal-foundation, 소비 포털 |
| 메뉴 | Ant Design Menu의 onClick과 leaf item onClick을 모두 연결 | PortalShell, navigation adapter |
| Shell 구조 | Layout → Header + body grid → Sider + Content 구조 사용 | PortalShell |
| 브랜드 | 좌측 전체 높이 Sider의 상단에 배치하고 접힘 상태에서 함께 축소 | PortalSidebar |
| 사이드바 | 로고, 메뉴, 접기 버튼, 역할 전환을 하나의 rail로 담당 | PortalSidebar |
| 콘텐츠 영역 | PortalMain은 grid의 두 번째 열을 명시적으로 차지 | PortalMain |
| SSR | Portal/Modal 계열 컴포넌트는 client mount 이후 동작 | PortalDialog, PortalExploreDialog |
| List | deprecated Ant Design List 대신 PortalList adapter 사용 | Foundation components |
| 공개 설정 | build-time 환경변수보다 runtime JSON/API 우선 | UiPlatform runtime config |
가장 중요한 장애 원인
Ant Design 전환 뒤 메뉴와 오른쪽 콘텐츠가 보이지 않는 현상이 발생했습니다. 최종 원인은 이벤트 누락 하나가 아니라, Shell의 실제 computed layout이 잘못 계산된 것이었습니다.
Chrome DevTools에서 .pf-shell__body를 확인한 결과는 다음과 같았습니다.
display: grid
width: 248px
grid-template-columns: 248px 0px
grid-template-rows: 972px 0px사이드바 폭만 남고 Content 열이 0px가 되었기 때문에 오른쪽 콘텐츠가 DOM에는 존재하지만 시각적으로 표시되지 않았습니다. 이 문제는 단순히 background, z-index, display를 덮는 것으로 해결하면 안 됩니다. Root Layout과 body grid가 전체 viewport 폭을 갖도록 구조에서 보장해야 합니다.
표준 Shell 레이아웃 계약
다음 구조를 모든 소비 포털에서 기준으로 사용합니다.
PortalShell root
└── Ant Design Layout: row flex, width 100vw, min-height 100vh
├── PortalSidebar / Sider: 248px, 화면 전체 높이
│ ├── 브랜드 로고와 제품명
│ ├── 접기 버튼
│ └── 메뉴 및 역할 전환
└── workspace Layout: column flex, 남은 폭
├── PortalTopNav / Header: 검색 및 사용자 메뉴
└── PortalMain / Content: 남은 높이필수 레이아웃 계약은 다음과 같습니다.
| 요소 | 필수 조건 |
|---|---|
| Root Layout | display: flex, flex-direction: column, width: 100vw, min-width: 0, min-height: 100vh |
| Body Layout | display: grid, width: 100%, min-width: 0, grid-template-columns: 248px minmax(0, 1fr) |
| Sidebar | 첫 번째 grid 영역, 기본 폭 248px |
| Main Content | grid-column: 2, min-width: 0, overflow: auto |
| Main Inner | width: 100%, min-height: 100%, box-sizing: border-box |
width: 248px, grid-template-columns: 248px 0px, height: 0px, visibility: hidden, opacity: 0가 발견되면 먼저 부모 Layout의 computed width와 grid track을 확인합니다.
브랜드와 레일 배치 기준
로고와 제품명은 좌측 전체 높이 Sider의 상단에 배치합니다. 이 구조에서 Sider는 단순한 메뉴 영역이 아니라 하나의 navigation rail입니다. Rail을 접으면 로고, 제품명, 접기 버튼, 메뉴가 동일한 접힘 상태를 공유해야 합니다.
<Sider>
<BrandMark />
<strong>{productName}</strong>
<CollapseButton />
<Menu items={items} />
</Sider>
<Layout>
<Header>
<Search />
<AccountMenu />
</Header>
<Content />
</Layout>제품명은 Foundation의 productName, 로고는 productMark를 사용합니다. 소비 포털이 별도 로고를 제공하더라도 PortalShell의 topNavStart 슬롯을 우선 사용하고, Shell 내부에서 동일 브랜드를 다시 그리지 않도록 합니다.
메뉴 이벤트 체크리스트
Ant Design Menu로 변환하는 adapter에서는 다음 두 경로를 모두 검증해야 합니다.
| 경로 | 검증 내용 |
|---|---|
| Leaf item | 각 item에 key와 onClick 또는 상위 Menu의 key handler가 존재하는지 확인 |
| Group child | 그룹 하위 item의 id가 Ant Design key로 보존되는지 확인 |
| Shell callback | 선택 시 onSelect, onNavigate, 소비 앱의 state setter가 호출되는지 확인 |
| Active state | 소비 앱의 activeNavigationId가 선택된 key와 동일한지 확인 |
| Mobile | mobile sidebar에서도 동일한 선택 callback이 호출되고 닫히는지 확인 |
메뉴 클릭 테스트는 단순히 DOM click 성공 여부만 보지 말고, 클릭 전후의 active id와 콘텐츠 제목을 검증해야 합니다.
소비 포털 적용 순서
첫째, 소비 포털의 portal-foundation 버전과 실제 설치된 tarball 또는 registry package의 버전을 확인합니다. lockfile의 integrity와 패키지 내용이 일치하지 않으면 배포 빌드가 실패하거나 이전 bundle이 서비스될 수 있습니다.
둘째, PortalShell에 전달하는 navigationItems, activeNavigationId, mainHeader, topNavStart, topNavEnd, userProfile을 확인합니다. 특히 소비 앱이 메뉴 id를 자체 tab id로 변환하는 경우 id 변환 표를 먼저 작성해야 합니다.
셋째, 소비 앱의 레거시 Shell CSS를 제거합니다. .pf-shell__body, .pf-shell-main, .ant-layout, .ant-layout-content에 대한 display, width, height, position, overflow override는 Foundation 계약과 충돌할 수 있습니다.
넷째, 데스크톱과 모바일에서 실제 브라우저 computed style을 확인합니다. React DOM에 콘텐츠가 존재한다는 사실만으로 렌더링 성공을 판단하지 않습니다.
다섯째, 배포 후 HTML이 참조하는 JS/CSS asset hash를 로컬 build 결과와 비교합니다. Git commit이 최신이어도 관리형 클라우드 또는 CDN이 이전 asset을 반환하면 검증 결과가 달라집니다.
검증 명령
pnpm install --frozen-lockfile --prefer-offline
pnpm run check
pnpm run test
pnpm run build
git diff --check배포 asset 확인:
curl -sS https://<consumer-portal>/ -o /tmp/portal.html
# HTML에서 /assets/index-*.js와 /assets/index-*.css 추출
curl -sS https://<consumer-portal>/assets/index-<hash>.js -o /tmp/portal.js
curl -sS https://<consumer-portal>/assets/index-<hash>.css -o /tmp/portal.css브라우저 검증 기준
최소한 다음 시나리오를 수행합니다.
| 시나리오 | 기대 결과 |
|---|---|
| 기본 메뉴 클릭 | active 메뉴가 바뀌고 오른쪽 콘텐츠 제목이 바뀜 |
| Portal Foundation Preview 클릭 | Preview 화면이 표시됨 |
| Public Runtime Settings 클릭 | 설정 화면이 표시됨 |
| 사이드바 접기 | 메뉴가 축소되고 Content 폭이 남은 공간으로 유지됨 |
| 브라우저 새로고침 | 동일한 active route 또는 초기 화면이 정상 표시됨 |
| 모바일 메뉴 열기 | overlay와 mobile Sider가 표시되고 선택 후 닫힘 |
| 테마 변경 | 콘텐츠와 Header의 foreground/background가 함께 변경됨 |
실패 시 진단 순서
- DOM 존재 여부를 확인합니다. 접근성 트리에 콘텐츠가 없으면 React 조건 분기나 state를 확인합니다.
- Root width를 확인합니다. Root가 viewport 전체를 차지하지 않으면 body의
minmax(0, 1fr)가 0px가 될 수 있습니다. - Body grid track을 확인합니다.
grid-template-columns의 두 번째 값이 0px인지 확인합니다. - Content width/height를 확인합니다.
width,height,min-width,min-height,overflow를 확인합니다. - Visibility를 확인합니다.
display,visibility,opacity,color,background-color를 확인합니다. - Asset version을 확인합니다. staging이 최신 commit의 JS/CSS hash를 반환하는지 확인합니다.
- 그 후에만 코드 수정을 합니다. 임시
!importantoverride는 원인 확인 전 사용하지 않습니다.
금지할 접근
| 접근 | 문제 |
|---|---|
소비 앱에 .pf-shell__body { display: grid !important } 추가 | Foundation과 소비 앱의 책임 경계가 무너짐 |
| Content가 안 보일 때 흰색 배경만 변경 | 0px grid track 문제를 해결하지 못함 |
| vendor tarball을 수정하고 lockfile을 갱신하지 않음 | 관리형 클라우드 frozen install에서 integrity 오류 발생 |
| 배포 성공 로그만 보고 검증 완료 처리 | 이전 asset cache가 남아 있을 수 있음 |
| DOM 텍스트 추출만으로 시각 검증 완료 처리 | width/height/visibility 문제를 놓칠 수 있음 |
| 제품 로고를 Header와 Sider에 동시에 렌더링 | Ant Design shell에서 브랜드 위치가 어긋남 |
변경 파일 및 참고 위치
| 파일 | 역할 |
|---|---|
vendor/hohyun94-portal-foundation-<ver>.tgz | UiPlatform이 사용하는 Foundation package. 버전 기준은 package.json dependencies 다(현재 0.6.156) |
client/src/pages/Home.tsx | 소비 앱 메뉴와 콘텐츠 state 연결 |
client/src/runtimeConfig.ts | runtime JSON/API 설정 로더 |
client/public/config/ui-center.json | 정적 fallback runtime 설정 |
client/src/components/RuntimeConfigManager.tsx | 관리자용 공개 설정 편집 UI |
drizzle/schema.ts | MySQL runtime config schema |
drizzle/schema.pg.ts | PostgreSQL runtime config schema |
인수인계 시 확인할 정보
다음 소비 포털을 전환할 때 아래 정보를 문서에 기록합니다.
| 항목 | 기록 내용 |
|---|---|
| 포털 이름 | 소비 포털의 서비스명과 저장소 |
| Foundation 버전 | package 버전, registry 또는 tarball 출처 |
| 메뉴 id 표 | 기존 route/tab id와 Ant Design key의 대응 |
| 브랜드 | Header에 사용할 productName과 productMark |
| runtime 설정 | JSON fallback, API endpoint, 관리자 저장 위치 |
| 인증 | 로그인 callback과 초기 loading 조건 |
| 배포 | build command, start command, asset cache 정책 |
| 검증 결과 | check/test/build 및 브라우저 시나리오 결과 |
| 잔여 위험 | 모바일, SSR, theme, Modal focus 등 미확인 항목 |
현재 UiPlatform의 기준 커밋
현재까지의 주요 기준 커밋은 다음과 같습니다.
| 커밋 | 내용 |
|---|---|
2e61097 | Portal Foundation Ant Design migration과 SSR/List 개선 병합 |
d684198 | CSS patch의 재현성 개선 |
5b624a6 | PortalMain Content 영역 명시 |
ce7da7a | Ant Design shell grid 구조 명시 |
cb937b3 | Root와 body full-width 보장 |
다음 소비 포털은 cb937b3 이후의 Foundation 적용 결과를 기준으로 시작하고, 좌우 우선 레이아웃과 Sider rail 브랜드 배치를 함께 적용해야 합니다.