𝝅번째 알파카의 개발 낙서장

screen

블로그 3차 리뉴얼

projects

React

count

개요 🔗

Next.js로 블로그를 다시 만든 뒤에, 새로운 글 작성 이외에 별다른 유지보수는 하지 않았었다. 나름의 이유는 있었던 것이, 일단 당장 쓰는 데 큰 문제가 없었고, 귀찮기도 했다. 적절한 컴포넌트를 구상하고 배치하는 게 여간 귀찮은 일이 아니기도 했고.

내 블로그에 몇 가지 문제점이 있었는데, 그 중 하나가 About 페이지에 아무 것도 없다는 점이다. 뭔가 나름의 블로그 소개를 작성하려고 했는데, 마땅한 아이디어가 없었기 때문. 그러다 문득, 괜찮은 아이디어가 하나 떠올랐는데, "About 페이지에 커밋 리스트를 표시해주면 괜찮지 않을까?"란 생각이였다. 나쁘지 않은 생각이였으므로 개발에 들어갔으나, 갑자기 블로그의 못난 부분들이 거슬리기 시작했다.

갑자기 못난 부분들이 보이는 게 너무나도 참을 수 없던 나는, 그렇게 예정에도 없던 블로그 리뉴얼 작업을 시작했다.




문제점 🔗

내가 생각한 블로그가 가진 문제점은 다음과 같다.

  • 어색한 다크테마 색상
  • 아직도 사라지지 않은 페이지 간 이동 로딩
  • 느린 빌드 시간
  • About 페이지 컨텐츠 미흡
  • 조잡한 모바일 네비게이션
  • Material-UI의 유명무실함

그 밖에 리뉴얼 과정에서 블로그의 많은 부분을 뜯어 고쳤다.




개편 내용 🔗

개편된 내용은 아래와 같다.



1. 테마 색상 정립 🔗

원래의 다크 테마는 약간 블루블랙같은 남색 계열을 사용했었다. 아마 Upbit나 Material-UI 다크 테마에 영향을 받지 않았나 실다.

그 당시엔 독특한 색감이라 좋았는데, 어느 순간 보기가 싫어졌다. 하지만 안타깝게도 색감 같은 순수 디자인에 조예가 전혀 없던 나는, 고민 끝에 기존에 잘 운영 중인 다크테마의 색상을 참고하기로 했다.

그 대상은 개발자의 영혼의 단짝인 GitHub.

image

GitHub 다크 테마의 색상을 참고하여 색상을 다시 잡았다. 헤더의 그라데이션도 그냥 없애버렸다. 색감을 잘 맞추면 모를까, 테마랑 색감이 어울리지 않다보니 너무 촌스러웠다. 다시 말하지만, 난 색상에 전혀 감이 없다.

  • 기대 효과
    • 테마 정리


2. classnames 라이브러리 적용 🔗

React에서 SCSS 모듈을 사용하는 방법은 통상 아래와 같다.

SCSS

0.root {
1 background-color: gainsboro;
2}

TSX

0import styles from './App.module.scss';
1
2function App(): JSX.Element
3{
4 return (
5 <div className={styles.root}>Lorem ipsum</div>
6 );
7}

기본적으로 위와 같이 객체 형태로 사용한다. React에서 *.module.scss는 빌드 과정에서 고유 클래스명으로 변환하여 SCSS와 JSX에 적용한다. 이러한 이유로 SCSS 파일마다 같은 class를 사용함에도 다른 컴포넌트에 영향을 미치지 않는다.

내 블로그에선 Light, Dark로 모드가 나눠져있으므로, 두 모드를 변갈아 사용하기 위해선 아래와 같은 동작이 필요했다.

SCSS

0.root-dark {
1 background-color: black;
2 color: white;
3}
4
5.root-light {
6 background-color: gainsboro;
7 color: black;
8}

TSX

0import styles from './App.module.scss';
1
2function App(): JSX.Element
3{
4 const theme = themeState ? 'dark' : 'light';
5
6 return (
7 <div className={styles[`root-${themeState}`]}>Lorem ipsum</div>
8 );
9}

위와 같은 방식이다. 테마값을 비교하고, styles에서 이에 맞는 코드를 호출한다. 보다시피, 그리 깔끔한 방식은 아니다.

하지만 classnames를 사용하면 이런 문제를 깔끔히 해소할 수 있다.

SCSS

0.root-dark {
1 background-color: black;
2 color: white;
3}
4
5.root-light {
6 background-color: gainsboro;
7 color: black;
8}

TSX

0import classNames from 'classnames/bind';
1import styles from './App.module.scss';
2
3const cn = classNames.bind(styles);
4
5function App(): JSX.Element
6{
7 return (
8 <div className={cn('root', themeState)}>Lorem ipsum</div>
9 );
10}

위와 같이 cn 메서드에 파라미터를 순차적으로 집어넣어 클래스명을 결합할 수 있다. 공식 문서에 따르면, 파라미터로는 문자열 뿐만 아니라 숫자나 객체 등의 다양한 타입을 지원하는 것으로 보인다. 물론 내 블로그엔 저 정도면 충분.

이로써 분기에 따른 다양한 클래스를 깔끔하게 호출할 수 있다.

여담으로, classnames는 회사 업무 중 한 프로젝트를 뒤적거리다가 알 게 됐다.

  • 기대 효과
    • 깔끔한 디자인 개발방법


3. 모바일 네비게이션 개선 🔗

데스크탑 모드는 화면이 커서 상관없지만, 모바일 모드는 필연적으로 화면이 작아지므로, UI의 배치나 크기에 좀 더 많은 신경을 써야한다.

내 블로그의 경우, 데스크탑 모드에서는 네비게이션을 헤더에 배치하고 있었으므로, 모바일처럼 화면이 작아지면 별도의 메뉴로 빼서 사용할 필요가 있었다.

그래서 대충 모바일 메뉴를 구현하긴 했었는데, 좀 조잡했다. 많이. 좀 보기 싫었지만, 당장 동작이 안 되는 것도 아니였고, 무엇보다 사이드바를 구현하기 귀찮았다. UI도 UI지만, 애니메이션도 구현해야한다는 부담감이 있었기 때문에..

결과적으로 그렇게 쓸 순 없어서, 전형적인 사이드바 형태로 UI를 구성하고 애니메이션은 CSS로 구현했다.

SCSS

0@keyframes slide-in {
1 from {
2 transform: translate(0px, 0px);
3 }
4
5 to {
6 transform: translate(-200px, 0px);
7 }
8}
9
10@keyframes slide-out {
11 from {
12 transform: translate(-200px, 0px);
13 }
14
15 to {
16 transform: translate(0px, 0px);
17 }
18}
19
20.header {
21 position: fixed;
22
23 top: 0px;
24 right: -200px;
25
26 width: 200px;
27 height: 100%;
28
29 transition: 0.3s;
30
31 &[data-show=true] {
32 @include slide-in-animation();
33 }
34
35 &[data-show=false] {
36 @include slide-out-animation();
37 }
38}

data-show 속성으로 구분하여 메뉴를 on/off 할 수 있도록 구현했다. width: 200px;, right: 200px;로 지정하여 화면 바깥에 숨어있도록 구성하고, data-show=true일 경우, 0.3초 동안 right: 0px;로 이동하는 방식이다. data-show=off일 경우 그 반대로 동작한다.

image

대충 이런 방식이다. 지금 만든 것도 엄청 완벽한 건 아니지만, 적어도 이전보단 훨씬 이쁘다.

  • 기대 효과
    • 발전된 모바일 네비게이션


3. 태그별 페이지 제거 🔗

내 블로그의 게시글엔 카테고리와 태그, 두 가지 구분이 존재한다. 카테고리는 게시글의 주제를, 태그는 해당 게시글이 가지는 주요 키워드를 명시한다. 이를 활용하여 카테고리별 게시글, 태그별 게시글을 보여주는 것이 의도였다.

의도는 좋다. 다양한 조건으로 게시물들을 모아볼 수 있었으니까. 하지만 좋은 의도가 반드시 좋은 결과로 이어지진 않는 게 현실이다. 의도"는" 좋았던 이 구성은, 각 페이지마다 별도의 빌드 과정이 필요하다는 SSG의 특성과 맞물려 함께 아모르파티를 추게 되는데...


SSR, CSR과 달리, SSG는 선언된 모든 페이지에 대한 빌드 과정이 요구된다. 게시글 페이지별 리스트는 물론이고, 카테고리별, 태그별 리스트까지 별도로 빌드를 수행해야한다. 사람들이 거의 쓰지도 않고, 신경도 안 쓰는 페이지로 인해 빌드 시간이 기하급수적으로 늘어나개 된다. 게시글이 늘어나면 늘어날 수록 이러한 현상은 더더욱 심각해진다.

더군다나, 태그 시스템으로 인해 너무 많은 페이지가 빌드된다. 예를 들어, 카테고리 A와 1, 2, 3이라는 태그를 가진 컨텐츠를 추가할 경우, 아래와 같은 빌드가 이루어진다.

  1. A 카테고리 페이지 빌드
  2. 태그 1 페이지 빌드
  3. 태그 2 페이지 빌드
  4. 태그 3 페이지 빌드

만약 기존에 존재하지 않는 카테고리나 태그일 경우, 이전에 없던 빌드 결과물이 하나 더 생기는 셈이다. 한 게시물이 여러개의 태그를 가질 수 있으므로, 게시글이 하나 추가될 때마다 쓸데없이 많은 페이지를 빌드할 가능성이 생긴다.

기하급수적으로 늘어나는 빌드 시간에 비해, 태그별 게시글의 사용은 거의 없다시피하다. 더군다나 이미 비슷한 카테고리라는 개념이 있는데다, 전용 메뉴로 접근성까지 높아 태그의 의미는 더더욱 낮은 셈이다.

아깝지만 과감히 삭제하고 SEO 키워드 용도로만 남겨뒀다.

  • 기대 효과
    • 빌드 과정 단축


4. 게시글 리스트 인피니티 스크롤 적용 🔗

기존의 게시글 리스트는 페이지네이션 방식을 차용했다. 하지만 페이지네이션을 사용하면서 여러 의문이 들었다.

  • 이게 정말 적절한 방식인가?
  • 과연 사용자들이 내 블로그에서 페이지를 일일히 눌러가며 게시글을 볼까?
  • 페이지네이션이 없다면 빌드 과정도 단축할 수 있는 거 아닌가?

신중히 생각한 결과, 페이지네이션은 요구되는 볼륨에 비해 기대값이 매우 낮다고 판단. 비교적 트렌디한 방식인 인피니티 스크롤으로 변경하기로 했다.

그런데 문제는, 기존 방식을 완전히 부정한다. 인피니티 스크롤을 적용하려면, 전체 게시글에 대한 메타 정보가 필요한데, 현재 내 블로그의 구조 상 애시당초 그런 건 관리하고 있지 않았다. 그렇다고 별도의 데이터 서버를 두는 것 또한 좋은 방향은 아니라고 생각했다.


이를 해결하기 위해 기존 로직을 뜯어 고쳤다. 페이지네이션을 과감히 없애고, 사전 빌드 시 게시글에 대한 메타 정보를 JSON으로 저장한다.

image

게시글 리스트는 렌더링 시 메타 정보를 분석하고, 이를 10개마다 분리하여 표현해줬다. 페이지는 상태값에 저장하고, 스크롤이 끝까지 내려가면 다음 그룹을 추가로 표현해주는 식. 부드러운 UX를 위해, 스크롤이 약 80% 정도가 될 경우에 다음 그룹을 추가하도록 했다. 이러한 조치는 사용자로 하여금 끊김없는 데이터 호출을 제공해줄 것이다.

이를 통해 빌드 로직은 줄이고 편의성은 더욱 높일 수 있었다. 물론 메타 정보가 너무 커질 경우엔 또 다른 문제가 발생하겠지만, 아마 그건 꽤 오랜 시간이 지난 이후에나 고민할 거리다. 게시글이 최소 몇 천 단위는 되어야 할 것 같은데, 1년 동안 posts로만 약 200개 가 좀 안 되는 글을 작성했다. 선형적으로 계산해도 10년이 지나야 2천개가 안 된다.

그 정도의 규모가 되면 별도로 검색엔진 서버를 구축하는 게 오히려 효율적일 거 같고, 10년 뒤에 내 스킬이 발전할 것이라는 희망적인 기대를 걸어볼 수도 있으니... 지금 당장의 규모에선 크게 신경쓰지 않기로 했다.

  • 기대 효과
    • 빌드 과정 단축
    • 트렌디한 UX


5. 검색 기능 추가 🔗

4번과 맞물려 발생하는 문제를 해결하기 위한 조치. 인피니티 스크롤의 가장 큰 단점은 원하는 게시글로 바로 이동할 수 없다는 데 있다. 페이지라는 개념이 모호하므로, 비교적 아래에 위치한 게시글에 접근하기 위해선 스크롤을 하염없이 내려야한다.

또한, 게시글을 내리면 내릴수록 한 페이지에 표현되는 DOM이 늘어나므로, 브라우저의 부담도 같이 늘어난다. 즉, 페이지가 지속적으로 느려진다는 뜻이다.

이를 방지하기 위해, 사용자가 원하는 게시글로 바로 접근 가능하도록 추가적인 조치가 필요하다. 이러한 조치로 가장 적절한 것이 검색 기능이라 판단, 게시글 리스트에 검색 기능을 붙여 사용자가 원하는 게시글을 검색하고, 관련 리스트를 표시하도록 구성한다.

4번 작업을 수행하면서 메타 정보를 별도의 JSON으로 출력했으므로, 이를 통해 검색을 구현할 수 있을 것이다.


방법은 아래와 같다.

  1. 키워드 검색
  2. 메타 정보 분석
    1. 제목
    2. 요약
    3. 키워드
  3. 키워드를 포함하는 정보를 가진 게시글을 리스트로 출력

이와 같은 식이다. 여기서 키워드는 배열인데, 배열 검색은 연산 복잡도가 늘어나므로, 각 아이템을 공백으로 이어 하나의 문자열로 만들어서 사용한다.

TYPESCRIPT

0const keyword1 = [ '키워드1', '키워드2', '키워드3' ];
1const keyword2 = keyword1.join(' ');
2
3const hasMatch1 = keyword1.filter(item => item.includes(keyword)).length > 0;
4const hasMatch2 = keyword2.includes(keyword)

keyword를 포함하는 문자열을 가진 게시글을 찾을 경우, 키워드 배열 keyword1과 이를 문자열로 만든 keyword2의 차이. 확실히 keyword2 쪽이 훨씬 간단하다.

image

이렇게 검색 기능을 통해 게시글을 쉽게 접근할 수 있도록 유도가 가능하다.

  • 기대 효과
    • 편의성 제공
    • 태그의 존재 의의 확보


6. Material-UI 제거 🔗

블로그 리뉴얼 때 중점적으로 사용했던 건 Material-UI라는 UI 프레임워크였다. React 기반의 다양한 디자인 컴포넌트는 심미적 니즈 뿐만 아니라 기능적인 니즈 또한 충족시켜줬다. 하지만, Material-UI의 경험이 마냥 행복하지만은 않았는데, 이유는 아래와 같다.

  1. 너무 과대한 볼륨, 이로 인한 렌더링 작업 증가
  2. 내 SCSS 스킬 향상에 따른 무의미화
  3. 그대로 가져다 쓸 게 아니라면, 결국 추가적인 작업이 필요한 컴포넌트 구조

Material-UI를 써보면 알겠지만, 사용법도 살짝 복잡하고 규모도 커 보인다. 그래서인지, 내가 필요한 컴포넌트를 사용하기 위해 내부적으로 많은 JS, CSS를 호출한다고 느꼈다. 이는 결국 렌더링 비용으로 이어졌다. 안 그래도 단순한 페이지 이동으로도 로딩 이미지가 표시되는 게 이해가 안 됐는데, 이 원인이 Material-UI가 아닐까하는 생각이 자꾸 들었다.

또한, 내가 SCSS를 접한 뒤로 지속적으로 사용한 덕분에, SCSS 스킬이 많이 늘었다. 그말인즉슨, 그냥 내가 직접 디자인 짜는게 성능적으로나 유지보수 측면으로나 훨씬 낫다는 계산이 나온다는 뜻이다.

더군다나 Material-UI의 컴포넌트를 그대로 가져다 쓸 게 아니라면, 필연적으로 지저분한 코드를 작성해야한다. Material-UI의 몇몇 CSS 코드는 최우선순위를 가진 style 속성에 할당된다. 이를 SCSS로 덧씌울려면 스타일 항목마다 !important를 붙여야하는 참사가 일어난다. 이는 반응형 코드를 작성할 때도 상당히 불편했다.


여러가지 내/외부적 문제가 맞물리면서, Material-UI가 더 이상 필요없다는 결론을 내렸다. 거의 모든 컴포넌트에 사용 중이였던 Material-UI를 과감히 제거하고 순수 HTML 태그 및 SCSS 기반으로 컴포넌트를 재작성했다.

이 외에도 많은 변화가 동시다발적으로 일어나서, 렌더링 속도에 영향이 있었을지는 확실하지 않다. 하지만 한 가지 확실한 건, 페이지 이동 시 잠깐이라도 떴던 로딩 시간이 아예 사라졌다.

  • 기대 효과
    • 빌드 시간 단축(아마도?)
    • 렌더링 대기 시간 제거


7. React 18 적용 🔗

React 18 버전이 릴리즈됨에 따라 React 버전 및 Next.js를 업그레이드 했다. React 18로 옮긴 가장 큰 이유는 빌드 속도의 증가.

React의 공식 문서에 따르면, 컴파일 엔진을 Rust로 변경하여 5배 이상의 빌드 속도 향상을 기대할 수 있다고 한다. 이 수치가 내 블로그에 어떤, 얼마만큼의 영향을 줄 지 확실하지 않지만, 안 바꿀 이유가 없다고 판단하여 업그레이드를 수행했다.

BASH

0yarn upgrade react@latest @types/react@latest react-dom@latest next@latest

다행히 동작이나 빌드에 별다른 문제는 발생하지 않았다.

  • 기대 효과
    • 빌드 시간 단축(아마도?)
    • 최신 버전 사용


8. SEO 친화적인 태그 활용 🔗

기존의 블로그는 대다수의 링크 컴포넌트를 a가 아닌 button 태그로 사용했었다.

SPA 라우팅을 적용해야하는데, a 태그는 그냥 페이지 이동을 시켜버린다는 게 이유였다.

추후 next.js의 컴포넌트 Link를 알게 된 이후, 더 이상 button을 활용할 이유가 없다고 판단했다.

TSX

0function App(): JSX.Element
1{
2 return (
3 <Link href='/{page}'>
4 <a>페이지 게시글</a>
5 </Link>
6 )
7}

그 뿐만 아니라, SEO 엔진은 a 태그를 읽고 분석할 수 있으므로 SEO 측면에서도 링크는 a 태그를 활용하는 것이 현명하다는 결론에 도달, 모든 링크 태그를 Link로 교체했다.

  • 기대 효과
    • SEO 친화적인 사이트


9. URL 정책 변경 🔗

이건 좀 웃기긴 한데... 예전에 리뉴얼을 하면서 이런 언급을 했었다.

image

저 이후로 실제로 URL 정책을 쿼리 방식에서 경로 방식으로 전면 개편했었다.

이후 당분간 잘 쓰고 있었지만...

image

짜잔~ 하지만 "절대"라는 건 없군요!

인피니티 스크롤을 적용하게 되면서, 지금과 같은 경로 방식이 아닌 쿼리 방식으로 회귀해야했다.

  • 기존
    • /posts/1
    • /posts/TypeScript/3
  • 현재
    • /posts?page=1
    • /posts?page=3&category=TypeScript

인피니티 스크롤을 적용함에 따라 페이지마다 빌드를 할 이유가 없어졌다. 대신 쿼리스트링값을 비교하여 페이지와 카테고리를 표시한다. 페이지가 3일 경우, 총 30개의 게시글이 리스트에 렌더링되는 방식.

  • 기대 효과
    • SEO 친화적인 사이트


10. 카테고리 정책 변경 🔗

카테고리의 경우, 정책 변경이 있었다. 기존의 카테고리는 카테고리 당 하나만 선택할 수 있었다. 그도 그럴 것이, 카테고리별 리스트를 각각 빌드해야했기 때문에, 다수의 카테고리를 고려할 경우 빌드할 페이지의 갯수가 기하급수적으로 증가하기 때문.

image

하지만 인피티니 스크롤을 적용하면서 리스트의 빌드 과정이 완전히 삭제됐고, URL 또한 쿼리 기반으로 변경함에 따라 다수의 카테고리를 선택할 수 있도록 변경했다.

  • 기대 효과
    • 편의성 제공


11. 게시글 그룹 UI 변경 🔗

게시글 그룹 UI를 좀 더 직관적이고 간략하게 표현했다. 기존의 경우 너무 많은 정보를 보여주려고 한 흔적이 있어서, 게시글 그룹의 전체적인 리스트를 한 눈에 보기 어려웠다.

image

리스트 기반으로 UI를 변경했다.



12. 그 밖의 여러 UI 요소들 🔗

그 외에 여러 자잘한 부분에서의 변경점이 있었다.

  • 데스크탑 모드 Header 높이 조정
  • About 페이지 내용 추가
  • Comments 신규 페이지 추가
  • ArtBox 새로고침 기능 추가
  • 폰트 호출 방식 CDN으로 변경
  • 그 외 기타 등등

리뉴얼 덕분에 그 동안 쌓아놨던 불안정한 요소들을 개선했다.




성과 🔗

  • 빌드 시간 단축
    • 집 컴퓨터 기준 약 70s 정도 걸리던 빌드 속도가 약 20s 초중반 수준으로 감소함
    • 게시글 리스트 페이지를 제외한 게 큰 것 같음
  • 렌더링 지연 시간 제거
    • 페이지 이동 시 렌더링으로 인해 짧게나마 보이던 로딩 이미지가 아예 안 보일 정도로 빨라짐
    • Material-UI를 제외한 게 크지 않았나 싶음 (블확실)
  • 인피니티 스크롤 적용
    • 무의미한 페이지 제거
    • 트렌디한 방식 적용
  • 태그 제거
    • 무의미한 페이지 제거
  • 다중 카테고리 선택
    • 다양한 범주의 선택지 제공
  • 검색 기능 추가
    • 원하는 키워드 검색
  • SEO 강화
    • 이전보다 더욱 검색엔진 친화적인 사이트 구성
  • 개발 편의성 확보
    • 미사용 코드 제거
    • 노후화된 로직 개선
  • 블로그 디자인 강화
    • 일부 노후화된 디자인 개선
    • 누락된 컴포넌트 추가



여담 🔗

이번 리뉴얼을 진행하면서 인피니티 스크롤같은 트렌디한 기술을 사용하기도 했고, 전체적인 디자인 로직이나 내부 동작 로직을 많이 개선했다.

이직 후 오랜 시간이 지나진 않았음에도, 그 짧은 시간동안 다양한 기술이나 라이브러리를 접할 수 있었고, 그 것들을 이번 리뉴얼에 많이 녹여내고자 했다.

이거 하겠다고 주말평일 구분없이 한 4일 가까이 새벽 4시에 잔 것 같다. 오랜만에 집중에서 무언가 한 것 같다.

다음엔 뭐 하지...