ADR-0009: 레이아웃을 골격 2종과 표면 2종으로 나누고 골격만 템플릿으로 분기한다
상태
Accepted
날짜
2026-08-10
맥락
애플리케이션에는 성격이 다른 화면 골격이 필요합니다. 공개 소개 화면, 로그인 화면, 인증 후 업무 화면, 문서 화면이 각각 다른 구조를 갖습니다.
이들을 어떻게 표현할지가 문제입니다. 하나의 레이아웃 컴포넌트에 CSS 변수로 변형을 주는 방식과, 구조가 다른 것마다 별도 템플릿을 두는 방식이 있습니다. 전자가 마크업을 하나로 유지해 유리해 보입니다.
이 판단을 뒤집는 사실이 있습니다. 골격에 따라 스크롤 컨테이너의 위치가 달라집니다.
| 골격 | 스크롤 컨테이너 | 스크롤바 위치 |
|---|---|---|
| 상단 우선 (topbar) | 문서 전체 | 뷰포트 오른쪽 끝 |
| 측면 우선 (sidebar) | 콘텐츠 박스 | 콘텐츠 영역 안쪽 |
스크롤 containment는 어느 요소가 overflow를 갖는가의 문제이므로 CSS 변수로 전환할 수 없습니다. 단일 마크업으로는 topbar의 "뷰포트 끝에 붙는 스크롤바"를 표현하지 못합니다.
결정
축과 조합
레이아웃 종류는 랜딩 · 인증 · 앱셸 · 문서형 넷입니다. 이 중 앱셸과 문서형만 아래 두 축을 갖습니다. 랜딩과 인증은 네비게이션 구조가 없어 골격 축이 성립하지 않습니다.
| 축 | 값 |
|---|---|
| 골격 (frame) | topbar · sidebar |
| 표면 (surface) | bordered · inset |
topbar 골격은 bordered로 고정합니다. 문서 전체가 스크롤되는 구조에서 inset을 적용하면 콘텐츠 둘레의 배경이 스크롤과 함께 움직여 표면 경계가 스크롤 영역과 어긋납니다. 표면 선택은 sidebar에서만 유효합니다.
구현 방식
스크롤 컨테이너의 위치가 달라지는가?
예 → 구조. 템플릿 분기
아니오 → 페인트. 루트 data 속성 + CSS 변수골격은 구조이므로 레이아웃 컴포넌트의 템플릿 분기로, 표면은 페인트이므로 data-surface 속성과 CSS 변수로 구현합니다. 분기하는 것은 골격이며 내용물이 아닙니다. 네비게이션 항목, 헤더 컨트롤, 본문 슬롯은 두 템플릿이 공유합니다.
호출부의 무지
라우트는 레이아웃 컴포넌트 하나만 참조하며 어느 골격으로 렌더링되는지 알지 못합니다. 골격별 템플릿은 슬라이스 공개 API로 내보내지 않습니다.
이는 ADR-0007의 표현 컴포넌트 교체 패턴과 동일한 구조입니다. 같은 원리를 두 곳에서 쓰므로 규칙을 따로 익힐 필요가 없습니다.
세부 규칙은 레이아웃이 원본입니다.
근거의 성격
| 구분 | 내용 |
|---|---|
| 실측 | 골격에 따른 스크롤 컨테이너 차이와 flex 콘텐츠 열 붕괴는 작성자의 이전 프로젝트에서 브라우저 실측으로 확인된 것이며 본 표준이 승계합니다 |
| 선례 | topbar는 문서 사이트 관례(MDN · VitePress), sidebar는 앱 셸 관례를 따랐습니다 |
| 작성자 판단 | 표면을 2종으로 한정한 것은 이전 프로젝트에서 4종(bordered · tonal · floating · inset)을 운용한 뒤 내린 결론입니다 |
검토한 대안
골격까지 CSS로 전환 (grid-template-areas 변수)
| 구분 | 내용 |
|---|---|
| 장점 | 마크업이 하나로 유지되어 템플릿 중복이 없습니다. 전환이 리렌더 없이 즉시 반영됩니다 |
| 단점 | 스크롤 컨테이너의 위치는 어느 요소가 overflow를 갖는가의 문제라 변수로 표현되지 않습니다 |
| 기각 사유 | 실측에서 구조 차이로 확인되었습니다. 단일 마크업으로는 topbar의 뷰포트 끝 스크롤바를 만들 수 없습니다 |
표면 4종 유지 (bordered · tonal · floating · inset)
| 구분 | 내용 |
|---|---|
| 장점 | 변형 폭이 넓어 다양한 시각적 요구에 대응합니다 |
| 단점 | tonal은 "sidebar에서는 헤더가 본문과 한 장"이라는 골격별 조건부 매핑을 유일하게 요구했습니다. floating은 실사용 사례가 없었습니다 |
| 기각 사유 | 쓰지 않는 변형의 유지 비용과 조건부 규칙의 복잡성을 제거했습니다. 필요해지면 표면 추가는 CSS 변수 블록 하나로 가능합니다 |
라우트가 골격을 지정
| 구분 | 내용 |
|---|---|
| 장점 | 화면마다 골격을 다르게 줄 수 있어 유연합니다 |
| 단점 | 호출부가 구조를 알게 되어 골격 체계를 바꿀 때 라우트 정의를 전부 수정해야 합니다 |
| 기각 사유 | 화면이 골격을 아는 것은 계층 책임의 역전입니다. 레이아웃은 app 계층 소유이며 pages는 자신이 어디에 놓이는지 알지 않아야 합니다 |
결과
변형 축이 둘로 줄고 각 축의 구현 수단이 판정 기준 하나로 결정됩니다. 골격을 나중에 추가하거나 바꿔도 라우트와 화면은 수정하지 않습니다.
감수하는 사항은 다음과 같습니다.
- 골격별 템플릿이 두 벌 존재합니다. 공통 조각을 나눠 중복을 줄이지만 최상위 구조는 각자 유지합니다.
- 골격을 런타임에 전환하면 라우터 아웃렛이 재생성되어 화면이 다시 로드됩니다. 사용자가 전환 기능을 쓰는 빈도가 낮으므로 수용합니다.
sidebar골격은 라우터의 스크롤 위치 복원이 동작하지 않습니다. 내부 박스가 스크롤되기 때문이며, 이 골격을 선택하면 복원을 직접 구현해야 합니다.- 랜딩과 앱셸의 헤더가 별도 구현입니다. 공통 변경 시 두 곳을 수정합니다.