ADR-0015: 문서 본문 서식에 fumadocs 타이포그래피 프리셋과 shiki 를 쓴다
상태
Accepted
날짜
2026-08-12
맥락
문서 본문의 서식은 pages/docs 가 자체 CSS 140 줄로 정의하고 있었습니다. 마크다운이 만들어 내는 태그마다 규칙을 직접 작성한 것이며, 본 저장소가 문서 사이트를 목적으로 하는 이상 이 파일은 문서를 추가할 때마다 함께 자라는 구조였습니다.
두 가지 결함이 있었습니다.
코드 블록에 색이 없었습니다. 문서 34 개에 코드 블록 88 개가 있으며 그중 46 개가 TypeScript 입니다. 전부 단색으로 표시되어 예제의 구조를 눈으로 구분할 수 없었습니다.
Mermaid 다이어그램 2 개가 렌더링되지 않았습니다. 변환 파이프라인이 Mermaid 를 처리하지 않으므로 코드 원문이 그대로 노출되고 있었습니다.
문서 렌더링 영역의 시각적 기준으로 fumadocs 를 참조 대상으로 삼기로 했습니다. 화면 컴포넌트는 기존 디자인 시스템을 그대로 따르며, 마크다운이 렌더링되는 영역에 한해 별도 기준을 적용합니다.
결정
타이포그래피 프리셋을 외부에서 가져옵니다
@fumadocs/tailwind 의 typography 프리셋을 채택합니다. 본체인 fumadocs-ui 는 next: 16.x 와 react: ^19.2.0 을 요구하므로 사용할 수 없으나, 타이포그래피는 peerDependencies 가 tailwindcss: ^4.0.0 하나뿐인 별도 패키지로 분리되어 있습니다.
프리셋은 prose 유틸리티 하나를 생성하며 모든 규칙이 그 클래스의 하위 선택자로 중첩됩니다. Tailwind 4.1.12 로 실제 생성한 CSS 를 검사한 결과는 다음과 같습니다.
| 검사 항목 | 결과 |
|---|---|
| 전역 요소 선택자 | 없습니다. 추가분 전체가 @layer utilities 의 .prose 블록 안에 있습니다 |
| 등록 방식 | addComponents 와 addVariant 만 사용하며 addBase 를 호출하지 않습니다 |
| theme 확장 | 없습니다. 기존 유틸리티와 Spartan 토큰을 덮어쓰지 않습니다 |
| 추가 variant | prose- 접두사가 붙은 것뿐입니다 |
색은 프리셋이 참조하는 --color-fd-* 여섯 개를 우리 토큰에 연결해 공급합니다. 별칭은 :root 가 아니라 .doc-body 선택자 안에 둡니다. 문서 본문 밖에서 이 계보가 존재하지 않게 하여 다른 코드가 실수로 참조하는 경로를 막기 위함입니다.
코드 블록은 빌드 시점에 색을 입힙니다
build-docs.mjs 가 shiki 로 하이라이팅합니다. 런타임에는 하이라이터가 실행되지 않으므로 애플리케이션 번들이 늘지 않습니다.
| 항목 | 선택 | 사유 |
|---|---|---|
| 테마 | github-light 와 github-dark 를 함께 심음 |
화면에서 light-dark() 로 고르므로 토큰과 같은 방식으로 전환됩니다 |
| 배경 | 테마 제안값 대신 --muted |
코드 블록 테두리와 배경이 문서의 다른 요소와 같은 계보를 씁니다 |
| 미등록 언어 | 평문으로 처리 | 언어 태그 오타로 빌드가 실패하지 않습니다 |
프리셋은 인라인 code 에 테두리와 배경을 넣지만 pre 는 다루지 않습니다. fumadocs 본체가 코드 블록을 React 컴포넌트로 처리하기 때문입니다. 따라서 코드 블록의 외형은 pages/docs 가 직접 정의하며, shiki 출력에 not-prose 클래스를 붙여 인라인 code 서식이 상속되지 않게 합니다.
다이어그램은 화면에서 그립니다
Mermaid 블록은 빌드 시점에 원본과 그릴 자리만 만들고, 브라우저가 mermaid 로 SVG 를 그립니다. 다이어그램이 있는 문서를 열 때만 mermaid 청크를 내려받습니다.
번들 비용을 실측한 결과는 다음과 같습니다. mermaid 11 은 다이어그램 종류별로 자체 지연 로드하므로 전량을 받지 않습니다.
| 범위 | min | gzip |
|---|---|---|
| 전체 | 3,454kB | 943kB |
| flowchart 렌더 경로 24 개 청크 | 711kB | 185kB |
| 진입 청크 | 28kB | 11kB |
테마는 mermaid 가 SVG 안에 색을 구워 넣으므로 CSS 로 전환할 수 없습니다. 루트의 color-scheme 을 관찰해 모드가 바뀌면 다시 그립니다.
securityLevel 은 mermaid 기본값인 strict 를 유지합니다. 내장 dompurify 가 스크립트와 이벤트 핸들러를 제거합니다. 런타임 DOM 주입 경로가 하나 생기므로 조건은 ADR-0013 의 개정 절에 기록했습니다.
검토한 대안
자체 CSS 를 계속 확장
| 구분 | 내용 |
|---|---|
| 장점 | 의존성이 늘지 않습니다. 모든 규칙이 저장소 안에 있어 수정 경로가 짧습니다 |
| 단점 | 서식 완성도가 작성자의 타이포그래피 판단에 묶입니다. 표·인용구·목록의 세부를 직접 조율해야 합니다 |
| 기각 사유 | 본 저장소의 목적은 표준 문서의 집필이며 타이포그래피 설계가 아닙니다. 검증된 결과물을 가져오는 편이 목적에 부합합니다 |
MDX 도입
| 구분 | 내용 |
|---|---|
| 장점 | 문서 안에서 컴포넌트를 직접 호출할 수 있어 Tabs · Steps 같은 요소를 쓸 수 있습니다 |
| 단점 | MDX 는 JSX 를 React 컴포넌트 호출로 컴파일합니다. Angular 에는 대응 런타임이 없어 AST 를 Angular 컴포넌트로 매핑하는 렌더러를 직접 작성해야 합니다 |
| 기각 사유 | 문서 5,110 줄 중 표가 1,280 줄(25%)이고 이미지는 0 개입니다. 문서 규격이 인용 블록을 절당 1 개로 제한하므로 추가 컴포넌트 수요가 구조적으로 낮습니다. 파이프라인을 전면 교체할 근거가 되지 않습니다 |
fumadocs-ui 전체 채택
| 구분 | 내용 |
|---|---|
| 장점 | Callout · Tabs · 검색 · 사이드바를 완성된 상태로 받습니다 |
| 단점 | next: 16.x 와 react: ^19.2.0 을 요구합니다 |
| 기각 사유 | Angular 프로젝트에 설치할 수 없습니다 |
다이어그램을 표로 대체하고 Mermaid 를 쓰지 않음
| 구분 | 내용 |
|---|---|
| 장점 | 의존성이 늘지 않고 프리렌더 산출물에 내용이 전부 담깁니다 |
| 단점 | 요소 간 관계를 표로 옮기면 연결의 전체 형태를 한눈에 볼 수 없습니다 |
| 기각 사유 | 처음 판단의 근거였던 "대상이 2 개뿐"이라는 수치가 수요를 나타내지 않았습니다. 변환 파이프라인이 Mermaid 를 처리한 적이 없어 그 2 개도 원문이 노출된 상태였으므로, 쓰지 않은 것이 아니라 쓸 수 없었던 것입니다. 문서 규격이 다이어그램에 범례를 붙이도록 규정하는 이상 수단을 없애는 것은 규격을 구현에 맞추는 일이 됩니다 |
Mermaid 빌드 시점 SVG 생성
| 구분 | 내용 |
|---|---|
| 장점 | 프리렌더 산출물에 다이어그램이 포함되어 레이아웃이 늘어나지 않습니다. 열람 시 추가로 받는 것이 SVG 뿐입니다 |
| 단점 | @mermaid-js/mermaid-cli 가 puppeteer 를 필수 peer 로 요구합니다. 생성된 SVG 를 커밋해야 하며 다크 모드용 색을 따로 빼내야 합니다 |
| 기각 사유 | 생성물 비커밋 정책에 예외를 만들고 Chromium 을 의존성에 들이는 대가가, 열람 시 185kB 를 받는 비용보다 큽니다. 원본이 마크다운 안에 텍스트로 남는 편이 차이로 읽힙니다 |
결과
pages/docs 의 자체 CSS 가 140 줄에서 87 줄로 줄었고, 그중 30 줄은 라이선스 고지입니다. 실질 규칙은 여섯 개이며 전부 프리셋이 다루지 않는 영역입니다. 앵커 스크롤 여백, 표 가로 스크롤 래퍼, 코드 블록 외형, 토큰 별칭입니다.
감수하는 사항은 다음과 같습니다.
- 전역 스타일이 커집니다. 산출물 CSS 가 95.7kB 에서 107.2kB 로 11.5kB(12.0%) 늘었습니다.
- 생성 문서가 커집니다. shiki 가 토큰마다 두 테마의 색을 인라인 스타일로 심으므로 생성물이 497kB 에서 618kB 로 24.3% 늘었습니다. 문서 본문은 경로별 지연 청크이므로 첫 진입 비용에는 포함되지 않습니다.
- 서식의 원본이 저장소 밖에 있습니다. 표나 목록의 세부를 바꾸려면 프리셋 위에 덮어쓰는 규칙을 추가해야 하며, 프리셋 갱신 시 그 덮어쓰기가 어긋날 수 있습니다.
- 다이어그램이 프리렌더 산출물에 없습니다. 첫 표시 이후에 그려지므로 그 지점부터 본문이 아래로 밀립니다. 이동을 감추는 대신 높이를 0 에서 실제 값까지 전환해 보여줍니다. 예상 높이를 예약하지 않는 이유는 다이어그램마다 종횡비가 달라 예약값이 어긋나면 접혔다 펴지는 움직임이 되기 때문입니다.
- 다이어그램이 있는 문서는 열람 시 185kB(gzip)를 더 받습니다. 초기 번들에는 포함되지 않으며 다이어그램이 없는 문서는 이 청크를 받지 않습니다.
- mermaid 의 CommonJS 의존성 8 개를
angular.json의allowedCommonJsDependencies에 등록했습니다. 등록하지 않으면 빌드마다 경고가 8 줄 출력되어 실제 경고를 가립니다. 목록은 gantt 와 architecture 다이어그램이 쓰는 것들이며 flowchart 경로에서는 로드되지 않습니다.
프리셋은 MIT 라이선스(Copyright (c) 2023 Fuma)로 배포되며, 저작권 고지 전문을 pages/docs 의 CSS 상단에 유지합니다.