본문으로 건너뛰기

Swizzle로 디자인 커스터마이징하기

· 약 15분

1. 웅덩이 파는 데 포크레인 써보기 2. Design System 흉내를 위해 Figma 힐끗 보기 3. React 기초 훑어보고 Docusaurus 설치하기 4. Docusaurus config 톺아보기 (1) 5. Docusaurus config 톺아보기 (2)

6. Swizzle로 디자인 커스터마이징하기

7. Github Pages로 Docusaurus 웹사이트 호스팅하기 8. 구글 서치 콘솔 등록하고 사용자 인사이트 얻기

custom.css 활용하기

설치 직후에 /src/css/custom.css 경로에 가면 기본 생성되어 있는 CSS 파일을 찾을 수 있다. 이 곳에 작성된 CSS는 Docusaurus에서 이미 만들어놓은 스타일보다 우선순위가 더 높은 것으로 적용되기 때문에 내가 원하는 스타일로 덮어쓰기 할 수 있다.

색상 스타일 바꾸기

/src/css/custom.css
/**
* Any CSS included here will be global. The classic template
* bundles Infima by default. Infima is a CSS framework designed to
* work well for content-centric websites.
*/

/* You can override the default Infima variables here. */
:root {
--ifm-color-primary: #2e8555;
--ifm-color-primary-dark: #29784c;
--ifm-color-primary-darker: #277148;
--ifm-color-primary-darkest: #205d3b;
--ifm-color-primary-light: #33925d;
--ifm-color-primary-lighter: #359962;
--ifm-color-primary-lightest: #3cad6e;
--ifm-code-font-size: 95%;
--docusaurus-highlighted-code-line-bg: rgba(0, 0, 0, 0.1);
}

/* For readability concerns, you should choose a lighter palette in dark mode. */
[data-theme='dark'] {
--ifm-color-primary: #25c2a0;
--ifm-color-primary-dark: #21af90;
--ifm-color-primary-darker: #1fa588;
--ifm-color-primary-darkest: #1a8870;
--ifm-color-primary-light: #29d5b0;
--ifm-color-primary-lighter: #32d8b4;
--ifm-color-primary-lightest: #4fddbf;
--docusaurus-highlighted-code-line-bg: rgba(0, 0, 0, 0.3);
}

설치 직후의 custom.css에서 미리 설정된 색상 코드를 볼 수 있다. 여기서 내가 원하는 팔레트로 색상 코드값만 바꾸면 간편하게 색상 스타일을 바꿀 수 있다. 모든 색상을 직접 지정하려면 어디에 어느 색상 변수값이 사용됐는지를 확인해야 하기 때문에 색상 코드만 바꾸는 쪽이 더 편리하다.

레이아웃 잡기

Docusaurus는 Infima라는 프레임워크로 만들어져있다. Bootstrap을 써봤다면 금방 익숙해질 수 있다. div의 폭을 다루는 방법이 Bootstrap과 같기 때문에 index 페이지에 내 포트폴리오 내용을 구성할 때 Infima에서 제공하는 class명을 사용해서 쉽게 간격을 잡을 수 있었다.

클릭 이벤트 처리하기

script 파일(을 쓰려고 했지만)

index 페이지에 만든 애니메이션 버튼을 클릭하면 상세 내용이 나오고 다시 클릭하면 닫히게 만들었다.

포트폴리오를 넣어놓은 인덱스 페이지에 애니메이션이 필요했다. 상시 노출하자니 스크롤이 너무 길어져서 위 이미지처럼 버튼 클릭으로 숨겼다 보이게 하고 싶었다. 평소 이런 작업은 jquery 스크립 파일을 head에 붙여서 해결했기 때문에 head를 편집할 수 있는 방법을 찾아보았다. 결론을 먼저 말하자면 실패했는데, 가장 많은 삽질 시간을 보냈음에도 결국 해결책을 찾지 못했다.

우선 Docusaurus 가이드 페이지에서 script 파일을 붙이는 방법을 찾을 수 있다. docusaurus.config.js 파일을 편집하면 되는데, themeConfig가 아니라 config에 추가해야 한다. 처음엔 동작하는 것 같았는데 몇 번 새로고침을 하면 동작하질 않아서 꽤 오래 삽질해봤지만 해결되지 않았다.

또다른 방법으론 react-helmet을 쓰는 방법이 있다. head 정보를 바꾸도록 도와주는 라이브러리인데, 이 방법도 마찬가지로 처음엔 동작하지만 새로고침을 하고 나면 스크립트가 동작하질 않았다. 정확히는 head에 잘 들어가있던, 스크립트를 연결하는 한 줄의 코드가 새로고침하고 나면 사라졌다. react는 코드를 화면에 그리는 방식이 단순한 html + js 구조와는 다른데, 그래서 새로고침하면서 코드가 사라지는게 아닐까 싶었다. 더구나 index.js에 react-helmet으로 작성한 코드를 넣어서 그런지 index를 제외한 다른 서브 페이지에서는 head가 편집되지 않았다.

index.js에 클릭 이벤트 함수 바로 넣기

index.js도 하나의 js 파일이기 때문에 여기에 바로 클릭 이벤트 함수를 넣는 방법으로 해결했다. 아래는 내 index.js 파일의 일부이다.

src/pages/index.js
import React, {useState} from 'react'

... (중략) ...

function SectionProduct ({ product_desc, product_desc_img, feature_desc }){

const [isActive, setIsActive] = useState(false);
const handleProductBtnClick = () => {
setIsActive(!isActive)
}
const btnCustomClassName = 'btn-product-title bg-color-empty has-icn-true has-label-false'
const stateClassName = isActive ? 'clicked' : ''
const btnClassName = `${btnCustomClassName} ${stateClassName}`

return(
<div className='SectionProduct'>
<div className='product-title-wrapper'>

<a className={btnClassName} onClick={handleProductBtnClick}>
<div className='icn icn-arrow'></div>
</a>
</div>

... (중략) ...

</div>)

버튼이 위치한 Component를 그리는 함수인데, 버튼에 onClick 속성을 넣고 클릭할 때마다 class 이름에 clicked를 넣었다 삭제했다 하는 함수를 추가했다. React state를 사용하므로 맨 상단 import 목록에 useState를 추가해주어야 한다. 간단한 코드는 ChatGPT에게 물어보면 공부할 필요도 없다. 다만 이 방법도 약간의 한계는 있는 게, index.js에 클릭 이벤트 처리가 들어가므로 당연히 index 페이지가 아닌 다른 페이지는 관여할 수 없다. 이미 있는 UI는 CSS로 꾸밀 수 있어도, 내가 원하는 새로운 element를 추가하려면 js를 써야 하기 때문에 blog에서 필요한 편집을 어떻게 할 지가 고민이었다. 그래서 blog와 관련해서 index.js와 같은 역할을 하는 파일을 찾아야 했다.

Swizzle 활용하기

Swizzle 사용법

index 페이지는 직접 내용을 구성할 수 있도록 index.js 파일을 바로 주기 때문에 쉽게 커스터마이징할 수 있지만, 그 외 페이지는 js 파일을 바로 찾아볼 수 없도록 되어 있다. 그래서 다른 서브 페이지 UI를 Docusaurus 기본값을 쓰지 않고 내 마음대로 바꾸려면 Swizzle이라는 방법을 써야 한다. 쉽게 설명하면 Swizzle은 Docusaurus에서 이미 만들어놓은 Component를 내 코드로 덮어씌울 수 있도록 하는 기능을 말한다.

npm run swizzle

프로젝트 폴더 위치에서 위 명령어를 실행하면 아래와 같은 화면을 볼 수 있다.

Swizzle 쓰는 과정 (1)

여기서 theme-classic을 선택하고 엔터를 누르면 아래와 같이 Docusaurus에 있는 Component 목록이 나타난다.

Swizzle 쓰는 과정 (2)

화살표로 쭉 내려가면 꽤 스크롤이 긴데, 여기서 볼 수 있는 Component 리스트는 Docusaurus Github에서도 볼 수 있다. 여기서는 내부 코드도 다 볼 수 있기 때문에 내가 원하는 UI 요소가 위치한 Component 이름이 무엇인지 Github에서 미리 찾아보고 Swizzle을 시작해야 한다. 코드를 하나씩 읽어보며 Component 구조를 파악하는 게 꽤 오래 걸리는 일이었다. Github에서는 검색을 파일명 단위로만 지원해서 로컬에 다운로드받은 후, 적당한 에디터에서 불러와서 검색해가며 Component를 파악했다.

터미널에서 Swizzle을 실행하면 각 Component 이름 옆에 Safe 혹은 Unsafe가 표시된다. Docusaurus 가이드에 따르면, 사용자가 Docusaurus component를 직접 편집하는 경우 추후에 Docusaurus 라이브러리 업데이트가 있을 때 업데이트 내용과 충돌할 수 있다고 한다. 이런 불편함이 있다는 걸 사용자가 감수해야 하고, 그래서 편하게 건드려도 되는 Component와 아닌 것을 구분해놓은 것이다. 물론 나중에 문제가 생기더라도 가장 중요한 사이트 콘텐츠에 문제가 생기는건 아니고 UI만 깨지는 셈이니, 너무 부담가질 필요는 없다.

Swizzle 쓰는 과정 (3)

앞의 단계에서 편집할 Component를 선택하고 엔터키를 누르면 WrapEject 중 어느 방법을 택할지를 묻는다. Wrap은 Docusaurus component를 사실상 건드리지 않으면서 내가 필요한 요소만 추가할 때 쓰는 방법이다. 본래의 Component에는 wrapper를 씌우고 그 wrapper 바깥에 내 코드를 넣는 식이기 때문에 내가 원하는 위치가 어디냐에 따라서 Wrap으로는 문제 해결이 되지 않을 수 있다.

그렇다면 Eject를 써야 하는데, Eject를 선택하면 Docusaurus component의 내용을 직접 편집할 수 있다. 당연히 Wrap에 비하면 추후 업데이트에 따른 충돌 가능성은 좀 더 있는 편이다.

Swizzle 쓰는 과정 (4)

Wrap과 Eject 중 한 가지를 선택하면 Unsafe component를 골랐을 경우에 한해서 경고 메시지가 나온다. 여기서 Yes를 택하면 아래와 같이 Swizzle이 완료되고, 내 프로젝트 폴더 아래 /src/ 경로에 theme 폴더가 생성된 것을 볼 수 있다. theme 폴더 안에는 내가 Swizzle한 component 파일들이 쌓인다.

Swizzle 쓰는 과정 (5)

블로그 포스트 목록에 포스트 번호 표시하기

블로그 목록에 포스트가 나열될 때 몇 번째 포스트인지 표시됐으면 좋겠다는 생각을 했다. 이런 동적 요소는 CSS로는 불가능하기 때문에 필요한 Component를 Swizzle로 Eject 했다. 먼저 전체 글 번호를 알아내기 위해 BlogPostItems를, 글 번호를 표시하기 위해 BlogPostItem을 가져왔다. BlogPostItems는 주어진 포스트 묶음을 화면에 순서대로 그리는 역할을 하고, BlogPostItem은 그렇게 그려진 각 포스트가 어떻게 구성되는지를 다루고 있다.

/src/theme/BlogPostItems/index.js
import React from 'react'
import { BlogPostProvider } from '@docusaurus/theme-common/internal'
import BlogPostItem from '@theme/BlogPostItem'
export default function BlogPostItems({
items,
component: BlogPostItemComponent = BlogPostItem
}) {
return (
<>
{items.map(({ content: BlogPostContent }, index) => (
<BlogPostProvider
key={BlogPostContent.metadata.permalink}
content={BlogPostContent}
>
<BlogPostItemComponent
customIndex={index}
customIndexLength={items.length}
>
<BlogPostContent />
</BlogPostItemComponent>
</BlogPostProvider>
))}
</>
)
}

위 코드는 Swizzle로 생성된 BlogPostItemsindex.js 파일이다. BlogPostItemComponent에서 customIndexcustomIndexLength가 내가 추가한 속성이다. 여기서 각 포스트의 index 값을 알기 위해 map 함수에 index 변수를 추가했다. 여기서 보낸 값은 BlogPostItem Component에서 전달받아 사용한다.

/src/theme/BlogPostItem/index.js
import React from 'react'
import clsx from 'clsx'
import { useBlogPost } from '@docusaurus/theme-common/internal'
import BlogPostItemContainer from '@theme/BlogPostItem/Container'
import BlogPostItemHeader from '@theme/BlogPostItem/Header'
import BlogPostItemContent from '@theme/BlogPostItem/Content'
import BlogPostItemFooter from '@theme/BlogPostItem/Footer'
// apply a bottom margin in list view
function useContainerClassName() {
const { isBlogPostPage } = useBlogPost()
return !isBlogPostPage ? 'margin-bottom--xl' : undefined
}
export default function BlogPostItem({
children,
className,
customIndex,
customIndexLength
}) {
const containerClassName = useContainerClassName()
return (
<BlogPostItemContainer className={clsx(containerClassName, className)}>
<div className='blog-post-index-wrapper'>
<div className='blog-post-index'>
<h5 alt='게시글 번호'>#{customIndexLength - customIndex}</h5>
</div>
</div>

<BlogPostItemHeader />
<BlogPostItemContent>{children}</BlogPostItemContent>
<BlogPostItemFooter />
</BlogPostItemContainer>
)
}

앞서 보낸 customIndexcustomIndexLength를 받을 수 있도록 BlogPostItem 함수에 변수를 추가했다. 그리고 BlogPostItemHeader 바로 위에 게시글 번호를 표시하도록 div를 추가했다. 바로 customIndex를 표시하면 글이 내림차순으로 정렬되어서 그런지 숫자가 반대로 나와서 전체 글 개수에서 index를 빼준 값을 사용했다.

꾸미기는 이제 끝!

이제 모든 커스터마이징도 끝났으니 서버에 배포할 차례다. 다음 포스트에서는 Github pages 사용법과 배포 과정을 다룰 예정이다.

1. 웅덩이 파는 데 포크레인 써보기 2. Design System 흉내를 위해 Figma 힐끗 보기 3. React 기초 훑어보고 Docusaurus 설치하기 4. Docusaurus config 톺아보기 (1) 5. Docusaurus config 톺아보기 (2)

6. Swizzle로 디자인 커스터마이징하기

7. Github Pages로 Docusaurus 웹사이트 호스팅하기 8. 구글 서치 콘솔 등록하고 사용자 인사이트 얻기