Docusaurus 증분 빌드 — 빠른 페이지 HTML 생성 방안
문제 정의
Docusaurus는 정적 사이트 생성기(SSG)로, 문서를 수정할 때마다 전체 사이트를 다시 빌드해야 한다.
현재 "Publish" 기능(POST /api/rebuild)은 docusaurus build를 실행하여 모든 페이지, 모든 로케일을 재생성한다.
현재 소요 시간
| 단계 | 설명 | 소요 시간 |
|---|---|---|
| Client compilation | 모든 페이지의 React 컴포넌트 번들링 | 10-20초 |
| Server compilation | SSR용 서버 번들링 | 5-10초 |
| Static generation | 모든 페이지 HTML 생성 | 5-10초 |
| 전체 | webpack cache 있을 때 | 20-40초 |
| 전체 | webpack cache 없을 때 (첫 빌드) | 2-5분 |
문제: 한 문서만 수정했는데 전체를 다시 빌드하는 것은 비효율적이다.
전체 빌드가 필요한 이유
Docusaurus는 React SSR + Hydration 방식으로 HTML을 생성한다:
MD/MDX 파일 → MDX 컴파일 → React 컴포넌트 → webpack 번들 → SSR → HTML
↑
사이드바, 네비게이션, 테마 등
모든 페이지가 서로 의존관계
단순히 개별 HTML 파일만 교체하면 React Hydration이 실패한다. 페이지 데이터는 JSON 형태로 script 태그에 포함되어 있고, JS 번들 해시가 달라지면 참조가 깨진다.
현재 적용된 최적화
이미 적용되어 있는 빌드 최적화 목록:
| 최적화 | 설정 위치 | 효과 |
|---|---|---|
| SWC 트랜스파일러 | docusaurus.config.ts → experimental_faster.swcJsLoader | Babel 대비 20-70x 빠름 |
| SWC JS 최소화 | experimental_faster.swcJsMinimizer | Terser 대비 빠른 압축 |
| SWC HTML 최소화 | experimental_faster.swcHtmlMinimizer | HTML 압축 속도 향상 |
| MDX 크로스컴파일러 캐시 | experimental_faster.mdxCrossCompilerCache | Client/Server 간 MDX 결과 재사용 |
| SSG 워커 스레드 | experimental_faster.ssgWorkerThreads | 병렬 HTML 생성 |
| Webpack 파일시스템 캐시 | webpackCachePlugin | 후속 빌드에서 컴파일 결과 재사용 (~577MB) |
--no-minify 플래그 | packages/engine/app.ts | Publish 시 압축 생략 |
| 로케일별 빌드 | /api/rebuild?locale=ko | 특정 로케일만 재빌드 |
| 캐시 워밍업 | 서버 첫 시작 시 자동 빌드 | 첫 Publish가 빠르게 실행됨 |
결론: 빌드 파이프라인 자체는 충분히 최적화되어 있다. 근본적인 문제는 전체 빌드 아키텍처에 있다.
해결 방안
방안 A: Dev Server 통합 모드 (권장)
Docusaurus의 개발 서버(docusaurus start)는 webpack-dev-server 기반으로, 변경된 파일만 증분(incremental) 빌드한다.
이를 Express API 서버에 통합하여 문서 수정 시 즉시 반영되도록 한다.
아키텍처
┌─────────────────────────────────────────────────────────────────┐
│ Express API 서버 (port 4001) │
│ │
│ /api/* → API 핸들러 (인증, 문서 CRUD, 검색 등) │
│ │
│ GET /* → ┌─────────────────────────────────────────┐ │
│ │ DEV_SERVER_ENABLED=true? │ │
│ │ │ │
│ │ YES → Proxy → Docusaurus Dev Server │ │
│ │ (port 3000, 자식 프로세스) │ │
│ │ 증분 빌드 (1-3초) │ │
│ │ │ │
│ │ NO → express.static('/build/') │ │
│ │ 기존 정적 파일 서빙 │ │
│ └─────────────────────────────────────────┘ │
│ │
│ POST /api/rebuild → 전체 docusaurus build (프로덕션 배포용) │
└─────────────────────────────────────────────────────────────────┘
동작 흐름
1. 사용자가 에디터에서 문서 수정 → POST /api/docs (파일 저장)
↓
2. docs/ 파일이 변경됨 → Dev Server가 파일 변경 감지
↓
3. Webpack 증분 빌드 (변경된 파일만) → 1-3초
↓
4. 사용자가 문서 페이지 접속 → Express가 Dev Server로 프록시
↓
5. 최신 내용이 즉시 반영된 페이지 응답
장점
- 빠른 반영: 문서 수정 후 1-3초 내 HTML 반영
- 정확성: Docusaurus의 공식 렌더링 파이프라인 사용 (React SSR, 사이드바, 네비게이션 모두 정상)
- 기존 구조 유지: Express API 서버 구조를 변경하지 않음
- 이미 검증됨:
editor:hmr스크립트로 이 방식이 작동함을 확인
단점
- 메모리 사용: Dev Server가 추가로 ~200-400MB 메모리 사용
- 개발 모드 출력: 프로덕션 최적화 없이 제공 (코드 미압축, 소스맵 포함)
- 안정성: Dev Server 크래시 시 복구 로직 필요
구현 핵심 사항
1. 환경 변수 추가 (.env)
# Dev Server 모드 (true = 증분 빌드 모드, false = 정적 파일 모드)
DEV_SERVER_ENABLED=true
DEV_SERVER_PORT=3000
2. Dev Server 생명주기 관리 (packages/engine/app.ts)
- 서버 시작 시
docusaurus start --port 3000자식 프로세스 시작 - 프로세스 크래시 시 자동 재시작 (최대 3회)
- 서버 종료 시 자식 프로세스 정리 (
SIGTERM) - 헬스체크: Dev Server 준비 완료 전까지 정적 파일로 폴백
3. 요청 프록시 (packages/engine/app.ts)
http-proxy또는 Node.js 내장http.request로 프록시/api/*요청은 프록시하지 않음 (API 핸들러가 처리)- WebSocket 프록시도 필요 (HMR 웹소켓 연결)
4. 이중 모드 운영
- 편집 중: Dev Server 프록시 모드 (빠른 반영)
- 배포 시:
POST /api/rebuild→ 전체 빌드 → 정적 파일 교체
방안 B: 커스텀 단일 페이지 MDX 렌더러
개별 MDX 파일을 직접 컴파일하여 HTML을 생성하는 방식.
아키텍처
문서 수정 → MDX 컴파일 (@mdx-js/mdx) → React SSR → HTML 생성
↓
/build/ 내 해당 HTML 파일 교체
장점
- Dev Server 불필요 (메모리 절약)
- 단일 파일 처리로 매우 빠름 (< 1초)
단점
- React Hydration 문제: Docusaurus의 데이터 로딩 방식과 호환 필요
- 사이드바/네비게이션 불일치: 전체 빌드 없이는 메뉴 구조가 업데이트되지 않음
- Docusaurus 내부 의존성: 테마 컴포넌트, 플러그인 파이프라인을 재현해야 함
- 유지보수 부담: Docusaurus 업데이트마다 호환성 확인 필요
결론: 구현 복잡도가 높고 Docusaurus 업데이트에 취약하여 비권장.
방안 C: Docusaurus v4 + Rspack (미래)
Docusaurus v4는 webpack을 Rspack(Rust 기반 번들러)으로 교체할 예정이다.
기대 효과
| 항목 | webpack (현재) | Rspack (v4) |
|---|---|---|
| 전체 빌드 | 20-40초 | 4-8초 (5-10x 향상) |
| 증분 빌드 | 3-5초 | 0.5-1초 |
| 메모리 사용 | ~800MB | ~400MB |
현재 상태
docusaurus.config.ts에future.v4: true이미 설정됨@docusaurus/faster패키지 설치됨- Docusaurus v4 정식 릴리즈 시점은 미정
준비 사항
future.v4: true유지experimental_faster설정 유지 (v4에서도 호환)- v4 안정 릴리즈 시 업그레이드
비교 요약
| 항목 | 방안 A: Dev Server | 방안 B: MDX 렌더러 | 방안 C: v4 + Rspack |
|---|---|---|---|
| 반영 속도 | 1-3초 | < 1초 | 0.5-1초 |
| 구현 복잡도 | 중간 | 높음 | 낮음 (업그레이드) |
| 정확성 | 완벽 (공식 파이프라인) | 불완전 | 완벽 |
| 추가 메모리 | ~200-400MB | 최소 | 현재보다 감소 |
| 유지보수 | 낮음 | 높음 | 낮음 |
| 적용 가능 시점 | 즉시 | 2-3주 개발 | v4 릴리즈 후 |
| 권장 여부 | 권장 | 비권장 | 장기 계획 |
구현 완료 (방안 A: Dev Server 통합)
수정된 파일
| 파일 | 변경 내용 |
|---|---|
packages/engine/config.ts | devServerEnabled, devServerPort 설정 추가 |
packages/engine/app.ts | Dev Server 프로세스 관리, HTTP/WebSocket 프록시 미들웨어, 헬스체크 확장 |
packages/engine/index.ts | createApp() 반환값 변경 대응 (WebSocket 프록시 연결) |
packages/docunara/index.ts | createApp() 반환값 변경 대응 (WebSocket 프록시 연결) |
.env.example | DEV_SERVER_ENABLED, DEV_SERVER_PORT 변수 문서화 |
사용 방법
1. .env 파일에 설정 추가:
# Dev Server 증분 빌드 모드 활성화
DEV_SERVER_ENABLED=true
DEV_SERVER_PORT=3000
2. 서버 시작:
npm run editor:api
서버 시작 후 약 10-15초 후 Dev Server가 준비 완료되며, 이후 문서 수정 시 1-3초 내에 반영된다.
3. 상태 확인:
curl http://localhost:4001/api/health
# { "status": "ok", "dbEnabled": false, "devServer": { "enabled": true, "ready": true } }
동작 원리
- Express 서버 시작 시
docusaurus start --port 3000을 자식 프로세스로 실행 - Dev Server 준비 완료 시 (
compiled successfully감지) 프록시 활성화 /api/*외의 모든 GET 요청은 Dev Server로 HTTP 프록시- WebSocket
upgrade요청은 Dev Server로 TCP 소켓 프록시 (HMR 지원) - Dev Server가 준비되지 않았거나 응답 실패 시
/build/정적 파일로 폴백 - Dev Server 크래시 시 자동 재시작 (최대 3회)
- 서버 종료 시 Dev Server 프로세스 자동 정리
WebSocket 프록시 (HMR)
Docusaurus Dev Server는 HMR(Hot Module Replacement)을 위해 WebSocket 연결을 사용한다. 문서 수정 시 Dev Server가 WebSocket을 통해 브라우저에 변경 사항을 즉시 푸시한다.
브라우저 ←→ Express 서버 (port 4001) ←→ Dev Server (port 3000)
↑ ↑
WebSocket upgrade WebSocket 원본
HTTP → TCP 소켓 프록시 webpack HMR
이를 통해 Docusaurus SPA 클라이언트 사이드 라우팅(navbar 클릭 등)에서도 수정된 내용이 브라우저 새로고침 없이 자동 반영된다.
구현 방식: createApp()이 { app, onServer } 객체를 반환하며,
onServer 콜백은 HTTP 서버의 upgrade 이벤트를 가로채 Dev Server로
raw TCP 소켓 연결을 생성하여 양방향 파이프를 구성한다.
향후 개선 사항
- Docusaurus v4 + Rspack: v4 정식 릴리즈 시 업그레이드하면 전체 빌드도 5-10x 빨라짐