본문으로 건너뛰기

Docusaurus 증분 빌드 — 빠른 페이지 HTML 생성 방안

문제 정의​

Docusaurus는 정적 사이트 생성기(SSG)로, 문서를 수정할 때마다 전체 사이트를 다시 빌드해야 한다. 현재 "Publish" 기능(POST /api/rebuild)은 docusaurus build를 실행하여 모든 페이지, 모든 로케일을 재생성한다.

현재 소요 시간​

단계설명소요 시간
Client compilation모든 페이지의 React 컴포넌트 번들링10-20초
Server compilationSSR용 서버 번들링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.swcJsLoaderBabel 대비 20-70x 빠름
SWC JS 최소화experimental_faster.swcJsMinimizerTerser 대비 빠른 압축
SWC HTML 최소화experimental_faster.swcHtmlMinimizerHTML 압축 속도 향상
MDX 크로스컴파일러 캐시experimental_faster.mdxCrossCompilerCacheClient/Server 간 MDX 결과 재사용
SSG 워커 스레드experimental_faster.ssgWorkerThreads병렬 HTML 생성
Webpack 파일시스템 캐시webpackCachePlugin후속 빌드에서 컴파일 결과 재사용 (~577MB)
--no-minify 플래그packages/engine/app.tsPublish 시 압축 생략
로케일별 빌드/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.tsdevServerEnabled, devServerPort 설정 추가
packages/engine/app.tsDev Server 프로세스 관리, HTTP/WebSocket 프록시 미들웨어, 헬스체크 확장
packages/engine/index.tscreateApp() 반환값 변경 대응 (WebSocket 프록시 연결)
packages/docunara/index.tscreateApp() 반환값 변경 대응 (WebSocket 프록시 연결)
.env.exampleDEV_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 } }

동작 원리​

  1. Express 서버 시작 시 docusaurus start --port 3000을 자식 프로세스로 실행
  2. Dev Server 준비 완료 시 (compiled successfully 감지) 프록시 활성화
  3. /api/* 외의 모든 GET 요청은 Dev Server로 HTTP 프록시
  4. WebSocket upgrade 요청은 Dev Server로 TCP 소켓 프록시 (HMR 지원)
  5. Dev Server가 준비되지 않았거나 응답 실패 시 /build/ 정적 파일로 폴백
  6. Dev Server 크래시 시 자동 재시작 (최대 3회)
  7. 서버 종료 시 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 빨라짐