メインコンテンツまでスキップ

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.tsexperimental_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.tsfuture.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 빨라짐