2021년 2월, Vue 2 + BootstrapVue + Vue CLI(vue-loader 15) 환경에서 작성한 글이다. 2026년 기준으로 달라진 점은 다음과 같다.
- Vue 2는 2023년 12월 31일에 지원이 종료됐다. BootstrapVue도 Vue 2·Bootstrap 4 기반이라, Vue 3에서는 BootstrapVueNext를 쓴다.
- Vue 3에서는
::v-deep대신:deep()을 쓴다. 예:.main-wrapper :deep(.btn) { ... }- 아래 코드의
~bootstrap/...경로 표기는 webpack(Vue CLI) 전용이다. Vite에서는~없이bootstrap/...로 쓴다.
목차
목차 펼치기
scoped 스타일을 쓰는 이유
여러 라이브러리의 CSS를 규칙 없이 한 사이트에 불러오면, 스타일이 서로 충돌하거나 겹쳐서 원하는 결과를 얻기 어렵다. 프로젝트나 컴포넌트마다 scoped 스타일을 쓰면 이런 문제를 막을 수 있다.
상황: 기존 사이트의 Bootstrap 버전
LOOK360은 Bootstrap 3를 기반으로 만들어졌고, 관리자 화면 일부에만 Bootstrap 4를 쓰고 있었다.
사이트가 쓰는 CMS(kimsq 1.2)는 Bootstrap 3를 그대로 불러오면 레이아웃이 심하게 깨져서, Bootstrap CSS에 접두사(prefix)를 붙인 버전을 따로 만들어 쓰고 있었다.
왜 Vue 컴포넌트에 Bootstrap을 scoped로 넣어야 했나
Vue로 만든 화면을 기존 레거시 코드 대신 넣거나, 그누보드·kimsq 같은 CMS 안에서 쓰면 문제가 생긴다. Vue 쪽 Bootstrap CSS가 전역으로 적용되면 CMS의 기존 스타일과 충돌하고 겹친다. 그래서 Vue 컴포넌트 안에서만 Bootstrap이 적용되도록 범위를 묶어야 했다.
방법
- vue-loader에 SCSS 전처리기를 설치한다 (vue-loader 문서). Vue CLI라면 프로젝트를 만들 때 함께 설치할 수 있다.
- deep 선택자를 쓴다 (vue-loader 문서). scoped 스타일은 빌드할 때 컴포넌트의 요소에
data-v-xxxx같은 속성을 붙이고, CSS 선택자에도 그 속성을 붙여서 범위를 제한한다. deep 선택자 안에 Bootstrap CSS 전체를 넣으면 Bootstrap의 모든 규칙이 이 컴포넌트 범위로 제한된다. - SCSS의 deep 선택자 블록 안에서 Bootstrap CSS를 import한다 (참고한 글).
<style scoped lang="scss">
.main-wrapper ::v-deep {
@import "~bootstrap/dist/css/bootstrap.min";
}
</style>
이렇게 하면 Bootstrap의 .btn 같은 규칙이 .main-wrapper[data-v-xxxx] .btn 형태로 컴파일되어, .main-wrapper 안에서만 적용된다.
주의사항
- SCSS 같은 전처리기는 일반 CSS의 deep 선택자인
>>>를 제대로 해석하지 못한다. 전처리기를 쓸 때는>>>대신/deep/이나::v-deep을 써야 한다. 나는 위 예제처럼 SCSS에서::v-deep을 쓰니 정상적으로 동작했다. - 모달, 툴팁, 팝오버처럼
body바로 아래로 옮겨서 그려지는 요소는.main-wrapper밖에 있어서 스타일이 적용되지 않는다. 이런 요소는 따로 스타일을 지정해야 한다. - Bootstrap의
html,body대상 기본 스타일(Reboot)은.main-wrapper html처럼 바뀌어 적용되지 않는다. - 이 방식을 여러 컴포넌트에서 쓰면 Bootstrap CSS가 컴포넌트마다 중복으로 들어가 번들이 커진다. 최상위 컴포넌트 한 곳에서만 쓰는 것이 좋다.
정리
Bootstrap CSS나 BootstrapVue CSS를 전역으로 불러오면 기존 CMS의 레이아웃이나 레거시 코드가 깨질 수 있다. 기존 사이트 안에 Vue 화면을 넣을 때는 scoped 스타일로 범위를 묶어 이를 막는다.
SCSS의 deep 선택자 블록 안에서 Bootstrap CSS를 import하면, Bootstrap에 정의된 CSS 전체를 scoped로 묶을 수 있어서 충돌 문제가 사라진다.