안녕하세요! 올리브영 프론트엔드 개발자 쩡다입니다.
"하나의 콘텐츠를 만드는 데 정말 7일이나 걸려야 할까요?"
이 질문에서 시작된 올리브영의 콘텐츠 제작 프로젝트를 소개합니다.
직접 구축한 블록 기반 CMS로 제작 시간을 7일에서 20분으로 단축하고,
Vue에서 Lit 웹 컴포넌트로 전환하며 얻은 확장성 인사이트를 공유하겠습니다.
단순히 기술 전환의 이야기가 아니라,
- 기존 발행 방식이 가진 비효율성과 병목 지점
- CMS를 모듈화하면서 얻은 아키텍처 인사이트
- 프레임워크 종속성과 확장성 문제를 어떻게 풀었는지
이 과정을 정리해 보려 합니다. CMS 구축을 고민하는 개발팀이나 콘텐츠 워크플로우 개선이 필요한 분들께 도움이 되길 바랍니다.
7일 걸리던 콘텐츠 제작, 20분으로 단축하다
기존 발행 과정의 한계
기존 에디토리얼 콘텐츠 발행은 아래의 단계를 거쳤고, 하나의 콘텐츠가 완성되기까지 평균 7일 이상이 걸렸습니다.
- 콘텐츠 기획 → 디자인 → 퍼블리싱 → 콘텐츠 발행
특히 디자인과 퍼블리싱은 외주 인력이 담당하다 보니 커뮤니케이션에만 수십 시간이 소요되었고,
이 과정에서 다음과 같은 문제가 있었습니다.
- 제작 효율 저하: 같은 모양의 블록을 매번 새로 작성해야 함
- 수요 대응 불가: 인력이 제한되어 콘텐츠 수요를 따라가기 어려움
- 사용자 경험 한계: 발행량 부족으로 개인 맞춤형 추천 같은 고도화가 어려움
문제 해결을 위한 접근
이런 문제 때문에 CMS를 직접 만들기로 결정했습니다. 핵심 목표는 다음과 같았습니다.
- 퍼블리싱 제거 – 더 이상 HTML을 직접 작성하지 않도록 함
- 디자인 제거 – 모듈 단위 블록 시스템을 도입해 디자이너 개입을 최소화
- 누구나 제작 가능 – 기획자나 BPO도 개발자 도움 없이 콘텐츠 작성 가능
👉 즉, 블록 기반 CMS 에디터를 통해 "기획만 하면 바로 발행"이 가능하도록 구조를 바꾼 것입니다.
성과
- 콘텐츠 제작 시간이 기존 40시간 → 약 20분으로 대폭 단축
- 디자인/퍼블리싱 단계를 제거해 외주 커뮤니케이션 비용 감소
- 에디토리얼 콘텐츠 발행량이 약 2배 증가
- 현재 올리브영 내 발행 콘텐츠의 약 40% 가 CMS를 통해 생성
👉 이로써 콘텐츠 발행 주기가 크게 단축되었고, 사용자에게 더 풍부한 맞춤형 콘텐츠를 제공할 수 있는 기반이 마련되었습니다.
새로운 접근: 모듈화된 CMS 아이디어
저희가 세운 목표는 "누구나 빠르게 콘텐츠를 만들고 발행할 수 있는 시스템" 이었습니다.
이를 위해 다음과 같은 방향으로 설계했습니다.
- 모듈 단위 설계: 이미지, 텍스트 등 콘텐츠 요소를 독립 모듈로 제작
- 위치 이동: Drag & Drop으로 손쉽게 배치
- 필수 정보 입력 후 자동 생성: 디자인 및 퍼블리싱 리소스 없이 즉각적인 콘텐츠 발행 가능
👉 이렇게 하면 퍼블리셔 의존도가 크게 줄고, 콘텐츠 제작 속도도 눈에 띄게 빨라집니다.
첫 번째 구현: Vue 기반 CMS
처음에는 Vue를 활용해 CMS를 구현했습니다.
선택 이유:
- 팀에서 이미 Vue를 사용하고 있었고, 빠른 시제품 개발이 가능했기 때문
- 에디터는 contentEditable 대신 CKEditor5를 사용
contentEditable
은 표준이 아니어서 브라우저마다 태그 처리, 줄바꿈 방식,execCommand
동작 등이 달라 일관성 부족
구현 방법:
- 모듈 단위 Vue 컴포넌트 작성
- 부모 컴포넌트에서 상태 관리 및 렌더링
장점:
- 빠르게 프로토타입을 완성
- 실제 BPO/디자이너가 바로 써볼 수 있는 환경을 제공
한계:
- Vue 종속성 때문에 다른 프로젝트에서 재사용하기 어려움
- 유지보수 관점에서 확장성이 부족
- 기능이 늘어날수록 상태 관리 복잡성이 커짐
👉 이러한 한계점들을 통해 프로토타입 속도만큼이나 장기적인 재사용성과 확장성이 중요하다는 교훈을 얻었고, 이는 이후 Lit 기반 웹 컴포넌트 전환 결정의 핵심 근거가 되었습니다.

콘텐츠 구조: 블록 기반
콘텐츠는 블록 기반 구조로 저장됩니다. 즉, 단순히 HTML로 서술하는 방식이 아니라, 각 블록이 타입과 데이터를 가진 배열 형태로 나열됩니다.
예를 들어, 텍스트, 상품, 이미지 등의 요소가 각각 독립된 블록으로 정의됩니다.
[
{
id: "78421c00-7e4f-11f0-b7a6-6942e7596f9e",
type: 'text'
inputText: {
text: "<h2 class="fHead1">안녕하세요</h2><p style="text-align:center;">안녕하세요</p>"
}
},
{
id: "7f5ca6e0-7e4f-11f0-b7a6-6942e7596f9e",
type: "image",
inputImage: {
type: "singleType",
list: [
{ uid: "vc-upload-1755754022576-2", url: "image path" }
]
}
},
{
id: "92e1c0b0-7e4f-11f0-b7a6-6942e7596f9e",
type: 'productList',
inputProductList: {
price: 1000,
goodsName: '퍼퓸 파우터 팩트',
imagePath: 'image path'
status: '판매중'
}
}
]
두 번째 구현: Lit 기반 웹 컴포넌트
Vue에서 얻은 경험을 바탕으로, 지금은 Lit을 사용한 웹 컴포넌트 기반 CMS로 전환했습니다.
선택 이유:
- 프레임워크 독립적 → React, Vue, Angular 어디서든 재사용 가능
- 웹 컴포넌트 표준 기반 → 장기적 유지보수 유리
구현 방법:
- 이미지/텍스트/상품 모듈을 각각 Lit 컴포넌트로 제작
- 모듈 간 통신은 이벤트 기반으로 처리
- 상태는 중앙 store에서 관리 (필요 시 공유 가능)
- CMS 모듈을 npm 패키지로 배포 → 다른 프로젝트에서 라이브러리처럼 다운로드 후 사용 가능
기대 효과:
- 다양한 프로젝트에 쉽게 붙일 수 있음
- CMS 모듈이 라이브러리처럼 활용 가능
코드 구현 방식
웹 컴포넌트 정의: @customElement 데코레이터:
@customElement('article-editor-container')는 이 클래스를 브라우저가 인식할 수 있는 사용자 정의 HTML 태그인
로 등록하는 역할을 합니다.
이를 통해와 같이 HTML 문서에서 재사용 가능한 UI 요소로 사용할 수 있습니다.
상속과 스타일링: extends BaseComponent & static styles:
static override styles는 부모 클래스의 스타일에 추가로 현재 컴포넌트의 고유한 스타일을 적용하는 부분입니다.
unsafeCSS(styles)는 별도로 작성된 CSS 파일을 가져와 컴포넌트의 캡슐화된 스타일로 만들어 줍니다.
이를 통해 컴포넌트의 스타일이 전역 CSS와 충돌하는 것을 방지합니다.
반응형 속성: @property 데코레이터와 접근자:
이 속성은 컴포넌트의 다양한 설정을 담고 있는 객체입니다. 예를 들어, 어떤 모듈을 보여줄지(visibleModuleList) 등을 제어하는 데 사용됩니다. 이 또한 @property로 정의되어 있어 설정값이 변경되면 UI가 자동으로 갱신됩니다.
@customElement('article-editor-container')
export class ArticleEditorContainer extends BaseComponent {
static override styles = [...super.styles, unsafeCSS(styles)];
private _moduleList: ModuleItem[] = [];
@property({ type: Array })
get moduleList(): ModuleItem[] {
return this._moduleList;
}
set moduleList(value: ModuleItem[]) {
const oldValue = this._moduleList;
this._moduleList = [...value];
this.requestUpdate('moduleList', oldValue);
}
@property({ type: Object })
config: Config = {
visibleModuleList: [],
};
}
타 프레임워크 환경에서 라이브러리 설치 후 적용
<article-editor-container id="editor"></article-editor-container>
const config = {
visibleModuleList: [IMAGE, TEXT, PRODUCT],
};
const editor = document.getElementById('editor');
window.addEventListener('DOMContentLoaded', () => {
editor.config = config;
});
실제 사용 흐름 (초기 데모 영상)
마치며: 배운 점과 앞으로의 계획
"처음엔 그냥 빨리 만드는 게 목표였는데, 이제는 정말 쓸만한 도구가 됐네요."
함께 작업한 기획자분의 이 한마디가 이번 프로젝트를 가장 잘 요약하는 것 같습니다.
저희는 단순히 콘텐츠를 빠르게 만드는 것을 넘어, 사용자의 워크플로우를 근본적으로 개선하는 도구를 만들고자 했습니다.
핵심 인사이트
이 과정에서 얻은 가장 중효한 깨달음은 다음과 같습니다.
- CMS는 단순한 관리자 툴이 아니라 콘텐츠 제작 효율을 혁신하는 도구
- 프레임워크 종속적 구조보다 웹 컴포넌트 표준 기반의 독립적 구조가 장기적으로 유리
- 개발자에게는 코드를 짜는 것만큼 사용자의 문제를 깊이 이해하는 것이 중요
앞으로의 방향
앞으로는 이번에 구축한 편집기를 단순히 빠른 제작용 툴을 넘어 퀄리티 있는 콘텐츠 제작에 최적화된 도구로 발전시켜 나갈 계획입니다.
👉 CMS 구축을 고민하고 계신 분들에게 저희의 경험이 조금이라도 도움이 되었으면 좋겠습니다.
그리고 기술이 비즈니스와 만나 어떤 시너지를 낼 수 있는지 계속해서 고민하는 올리브영 테크 조직의 여정에도 많은 관심 부탁드립니다.