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

screen

블로그 개편기

posts

React

count

리모델링 🔗

이 블로그를 처음 개발한지가 한 5월 쯤으로 기억한다. 4월부터 접한 리액트에 흥미가 생겨 두서없이 만든게 시작이였으니, 결과물은 내 생각보다 훨씬 조잡하지 않았나 생각했다.

다행스럽게(?)도 블로그 또한 그런 내 믿음을 져버리기 싫었는지, 동작 과정에서 크고 작은 문제들을 보여주곤 했다.

동작 자체에 영향을 미칠 정도로 크리티컬한 문제는 아니였다만, 내 신경엔 영향을 미칠 정도로 거슬리는 부분이 몇 가지 있었다. 아무도 관심 없지만 이왕 만든거 이번 포스팅을 통해 블로그의 개선점을 정리해놓고자 한다.

문제점 🔗

크고작은 여러 문제점들이 있었는데, 목록은 아래와 같다.

1. 렌더링 지연 현상 🔗

여러 문제들 중 가장 가시적이고 불편한 문제였다. 어떠한 이유에서인지 이상하리만치 렌더링이 늦었다. 처음으로 페이지에 접근할 경우 렌더링하느라 버벅이는게 보일 정도. 사양에 따라서 CSS가 입혀지기 전인 날 것의 HTML 프레임이 그대로 보이기도 했다.

이 괴상한 렌더링 지연은 React의 고질적인 블랙박스 문제로 디버깅조차 잘 되지 않았다.

블랙... 뭔 박스요?
소스코드가 동작할 때, 어떤 특정한 모듈을 통해 동작하는 경우가 빈번하다. 잘 구성된 모듈은 은닉화, 모듈화가 잘 되어 있는 경우가 대부분이다. 하지만 이러한 모듈의 폐쇄성은 해당 모듈의 사용자로 하여금 모듈 내부의 오류로 인해 발생하는 이슈를 디버깅하기 매우 어렵게 만든다.
이러한 모듈의 은닉된 영역을 가리켜 블랙박스라 칭한다.

간혹 어떤 페이지는 렌더링이 두 번씩 되기도 했다. 설상가상으로 React의 다중 렌더링 문제는 꽤 고질적이라고 한다.

2. Unified.js과 관련 플러그인 커스터마이징 문제 🔗

해당 블로그는 JAMStack 기반 블로그다. 통상 JAMStack 기반 블로그의 경우 게시글을 Markdown으로 관리한다. 하지만 아쉽게도 웹 페이지는 HTML 기반으로 동작한다. 즉, Markdown에서 HTML로 적절히 변환해줄 로직이 필요하다.

JAMStack
JavaScript
API
Markup

단순히 변환만 해준다고 다가 아니다. 코드 하이라이팅을 위한 Prism.js, 수식 작성을 위한 LaTex 등 여러 플러그인을 적용해야한다.

하지만 Unified.js는 내 바램과 달리 공식 문서가 그리 친절하지도 않았으며, 커스터마이징을 위한 API 또한 찾아보기 힘들었다. 그 말인즉슨 remark-prism, remark-toc와 같은 플러그인에 의존해야 하는데, 정해진 형태로 변환만 해줄 뿐, 내가 끼어들어 커스터마이징할 여지는 없어보였다.

3. CSS-in-JS 방식 🔗

이 블로그의 CSS는 Material-UI를 적극적으로 사용했다. Material-UI는 CSS를 적용할 때, makeStyles와 같은 메소드를 통해 JavaScript 내부에서 스타일링을 하도록 안내하고 있다.

React를 독학으로 배운데다가, 그 체계에 익숙하지 않아 그 당시만 해도 이렇게 해야만하는 줄 알았다.

4. 부실한 RESTful URL 🔗

NextJS는 Dynamic Routing을 지원한다. 파일 이름을 [page].js와 같이 생성하고 getStaticPaths 메소드에서 page 변수에 적절한 값을 할당하면 해당 값을 가진 URL을 생성해준다.

GitHub에 호스팅하기 위해선 좋든 싫든 CSR 정적 방식을 차용할 수 밖에 없다. 때문에 내가 잘 하던 기존의 query 방식을 차용했었다.

게시글 2 페이지를 나타내는 URL이 /posts?page=2와 같은 방식이였다. 현재 트렌드로 미루어보아, 그리 좋은 방식은 아니지 싶다.

개선점 🔗

자잘한 문제들은 제외하고, 굵직한 것들만 생각나는대로 적으면 저 정도 나오는 것 같다.

블로그가 개발된 5월 이래로 개편을 시작한 7월 중순까지 그래도 나름 내적인 성장을 했던 건지, 이전보단 훨씬 나은 방향으로 개선할 수 있었다.

개선 내용은 아래와 같다.

1. TypeScript 적용 🔗

JavaScript의 가장 큰 장점이자 단점은 변수 타입의 모호성이다. 첫 언어를 C#, JAVA와 같이 경직된 객체지향 언어로 접한 내겐 거슬리는 부분 중 하나였다.

TypsScript

TypeScript는 JavaScript에 변수 타입이라는 개념을 도입함으로써, 변수의 모호함에서 유발되는 오류를 최소화한다.

Next.js는 TypeScript 기반의 템플릿을 제공해준다. TypeScript를 전혀 해보진 않았지만, "어차피 JavaScript에 타입 선언만 추가된 거 아닌가?"라는 생각이 들어 적용했다.

TypeScript에 적응하는 데 그리 오랜 시간이 걸리진 않은 걸로 기억한다. 내 생각대로 타입 선언 말곤 크게 달라진 게 없으니. 오히려 타입이 명시되니 해당 변수에 사용할 수 있는 올바른 내부함수라던가, 자동완성이 적용되서 훨씬 쾌적한 개발을 할 수 있었다.

단, 짜증나는 점이 하나 있었는데, 바로 라이브 서버와 컴파일 간의 차이였다. Next.js에서는 작성한 소스코드를 즉시 반영해주는 일종의 라이브 서버를 구동할 수 있다. 여기서 좀 짜증나는 차이가 발생하는데, TypeScript에서 오류가 나는 문장이나 문법이 라이브 서버에선 영향을 미치지 않는다. 즉, 보기에 멀쩡한 코드도 라이브 서버에선 실행까지 잘 되지만 빌드를 수행하면 갖가지 오류를 뱉어낸다.

타입이 정확하지 않아아아아아앙아ㅏㅇㄲㄲㄱㅇㅇ악!! 이딴 "불분명한"거 내 앞에서 치워!!!

이거 타입 선언 안 한 새x끼가 너냐?? ㅋㅋㅋㅋㅋㅋ 컴파일 오류 보고싶지 않으면 처---신 잘 하라고-

아니 이거 "null"일 가능성이 있는뎁쇼?? 아 렌더링 후에 반드시 생성되는 DOM이라 확실히 있는 놈이라구요? 아 DOM 그딴건 모르겠고 암튼 null일 수도 있다고 아ㅋㅋㅋㅋㅋㅋ

더 환장하는건, 실제론 동작 자체에 문제가 없는 소스다. JavaScript라면 무사통과할 수 있는 매우 정상적인 소스라는 뜻이다.

하지만 작고 소중한 TypeScript는 아무도 막을 수 없어서 조금만 의심스러운 부분이 있다면 바로 찡찡대기 시작한다.

특히 라이브러리의 내부 함수를 쓸 때가 좀 고역이였다. 정확한 타입을 선언해주기 위해서 소스 내부를 까서 어떤 타입의 파라미터를 받는지, 어떤 타입을 반환하는지를 확인해야했다. 물론 이게 TypeScript의 존재 의의다만...

이러한 문제에도 불구하고 TypeScript는 내가 JavaScript에서 불편하다고 느낀 것들을 해소해줬다. 문제는 전혀 다른 불편함을 준다는 점이지만..

그래도 나름 재밌는 개발 경험을 줬지 싶다.

2. CSS-in-CSS 적용 🔗

어렸을 때, 이런 문구 들어본 적 있을 거다.

진료는 의사에게, 약은 약사에게

의약분업을 위한 캐치프레이즈다. 내 블로그에는 통용되지 않는 말이기도 하다.

이전까지의 내 블로그는 CSS-in-JS가 적용되어 있었다. 위에서도 언급했듯이, 내 블로그의 CSS는 전적으로 Material-UI에 의존하고 있었다. 이로 인해 의존할 수 있는 래퍼런스 또한 많지 않았고, 불행히도 Material-UI는 CSS-in-JS 방식의 래퍼런스를 제공한 탓에 지금까지 이런 방식을 적용했었다.

CSS-in-JS? CSS-in-CSS??
CSS-in-JS: JavaScript에서 CSS 담당
CSS-in-CSS: CSS에서 CSS 담당

몰론 장점도 있었다. CSS-in-JS의 가장 큰 장점은 CSS를 JavaScript에서 관리하므로 CSS의 동적 생성이 쉽다. 더군다나 내 블로그는 다크, 라이트 모드를 토글할 수 있기 때문에 이러한 장점은 더더욱 강력하게 다가왔다.

하지만 위에서도 언급했듯이, 렌더링 및 성능 문제가 가장 큰 이슈로 대두됐고, 이 중 스타일링 구문이 가장 의심이 됐다.

모든 컴포넌트 마다 JavaScript에서 스타일링 구문을 생성해야했고, 게시글 같은 경우 스타일링 요소가 많아 JSX 코드보다도 스타일링 코드가 훨씬 더 길어지는 주객전도가 일어나기도 했다.

더군다나 CSS-in-JS에서 일어나는 치명적인 문제가 있는데, CSS가 적용되기 전인 날 것의 HTML이 잠깐 보이는 현상이다. 이러한 현상을 FOUC(Flash of Unstyled Content)라고 부른다.

NextJS에선 emotion.js를 적극적으로 차용하도록 유도하여 이러한 문제를 줄인다고 하는데.. 특별한 이유가 없다면 CSS는 CSS가 담당하는 게 맞다고 생각하여, CSS-in-CSS를 차용하기로 했다.

3. SCSS 적용 🔗

위에서 언급한 CSS-in-CSS를 위해, 처음엔 CSS를 사용하고자 했다. 하지만 사소한 문제가 하나 있었는데, 기존 소스와의 호환성을 위해선 CSS만으로는 안 된다.

JAVASCRIPT

0/**
1 * 스타일 객체 반환 함수
2 *
3 * @returns {JSON} 스타일 객체
4 */
5function getStyles()
6{
7 return makeStyles((theme) => ({
8 fab_bright: {
9 position: "fixed",
10 bottom: 50,
11 right: 50,
12 backgroundColor: grey[800],
13 color: grey[200],
14 "&:hover": {
15 backgroundColor: grey[700]
16 },
17 "& svg": {
18 color: orange[600]
19 },
20 [theme.breakpoints.up("md")]: {
21 "& span": {
22 marginLeft: theme.spacing(1)
23 }
24 },
25 [theme.breakpoints.down("sm")]: {
26 bottom: 70,
27 right: 20
28 }
29 },
30 fab_dark: {
31 position: "fixed",
32 bottom: 50,
33 right: 50,
34 backgroundColor: grey[200],
35 color: grey[900],
36 "&:hover": {
37 backgroundColor: grey[300]
38 },
39 "& svg": {
40 color: blue[600]
41 },
42 [theme.breakpoints.up("md")]: {
43 "& span": {
44 marginLeft: theme.spacing(1)
45 }
46 },
47 [theme.breakpoints.down("sm")]: {
48 bottom: 70,
49 right: 20
50 }
51 },
52 div: {
53 height: 24
54 }
55 }))();
56}

기존에 사용 중인 CSS-in-JS 소스는 이와 같다. Material-UI의 makeStyles를 활용한 것으로, 구조를 보면 알겠지만 일반적인 CSS와는 좀 다르다.

CSS

0.alpha {
1 color: red;
2}
3
4.alpha:hover {
5 color: blue;
6}
7
8.alpha .beta {
9 background: black;
10}

SCSS

0.alpha {
1 color: red;
2
3 &:hover {
4 color: blue;
5 }
6
7 .beta {
8 background: black;
9 }
10}

각각 동일한 동작을 CSS와 SCSS로 표현했다. 보다시피, makeStyles와 SCSS의 표기가 매우 흡사함을 알 수 있다. 즉, 구버전의 스타일링 시스템와 호환성을 최대한 높이기 위해선 SCSS 내지는 SASS와 같은 스타일 전처리기가 반드시 필요했다.

SCSS

그 중 내가 차용한 건 SCSS. SASS는 기존의 CSS와는 살짝 다른 문법을 가지고 있다는 점이 거슬렸다. SCSS는 단순한 스타일링 이외에도 @mixin, @for나 변수 선언과 같이 좀 더 프로그래밍적인 요소가 강해 더욱 간편한 스타일링 개발 경험을 제공했다.

일장일단이 있던 TypeScript와 달리, 앞으로도 계속 쓰고 싶을 정도로 마음에 들었다.

4. marked.js 차용 🔗

내가 제대로 알아보질 않아서 그런지 모르겠으나, 기존에 썼던 unified.js, remark, rehype 계열 플러그인의 경우 사용자가 커스터마이징할 여력이 없었던 걸로 기억한다. 때문에 이와 연관된 수 많은 플러그인을 설치해야했다.

더 골때리는건, 적용된 결과물을 커스텀할 명확한 방법을 제공해주지 않았다. 즉, 주는 대로 쓰라는 말인데.. 여러모로 달갑지 않았다.

특히 코드 블록의 경우, 복사 버튼이나 언어 표시 등 다채로운 기능을 추가하고 싶었으나, 그럴 수 없어 난감했다. 굳이 거창한 플러그인이 아니더라도, 변환된 HTML 구문만 던져주면 wrapper라도 하나 만들어서 감싸줄텐데, 그런 것도 없으니 여의치 않았다. 물론 전체 HTML은 갖고 있었다만, 이걸 일일히 파싱해서 추출한다는 것도 다소 무식하기도 하고.

그렇게 찾은 차선책이 marked.js다. 이유는 단 한 가지로, 사용자가 결과물을 기본적인 구문 혹은 직접 커스텀한 구문별로 자유로운 커스터마이징이 가능했기 때문.

그래도 Prism.jsLaTeX를 적용하는 건 녹록치 않았었다. 특히 LaTeX는 완벽히 적용하는데 3일 정도는 걸리지 않았나 싶다. LaTeX의 경우 로 감싼 구문에 적용되는데, 마크다운이 기본적으로 에 뭔가 특수한 구문이 적용되는게 아니기 때문에 해당 문자열 토큰을 구문별로 감지할 수 있는 로직을 작성해야했다.

그나마 Prism.js는 마크다운에 코드블럭 문법 자체가 있어서 그리 어렵지는 않았지만, LaTeX는 구문 분리부터 끝까지 다 만드느라 정말 힘들었다.

그래도 어쨌든 unified.js와 달리 커스터마이징 API가 잘 설명되어 있어서, 이런 것들도 만들 수 있었다.

JAVA

0public static void main(String[] args)
1{
2 // 이런 것도 구현했다.
3 System.out.println("여기에 텍스트 입력");
4}
  • 코드블럭 디자인
  • 언어 표시
  • 복사 버튼

코드블럭의 Mac 스타일 디자인은 외국 블로그들을 많이 참고했다. 간간히 구글링하다보면 좋든 싫든 외국 사이트에 접근하게 되는데, 저런 형식의 디자인이 많았다. 막상 직접 보면서 만드려고 하니 안 나오길래, 기억을 살려서 비슷하게 만들었다.

복사 버튼도 간단한 JS를 통해 그리 어렵지 않게 구현했다. 원래 Prism.js에 여러 플러그인이 있긴 한데, 리액트 상에서 적용하려니 잘 되지 않았다. 특히 라인 표시는 꼭 넣고 싶었는데, 다른건 어찌어찌 직접 만들었다만, 이런 류의 디자인은 좀 난이도가 있기도 하고, 라인을 세서 동적으로 넣어줘야 해서 따로 만들진 않았다. 나중에 여유가 되면 한 번 시도해볼 수도..?

또 하나, 테이블의 경우 중앙 정렬에 스크롤을 만들어주려면 반드시 div로 한 번 감싸줘야 했는데, 이 부분도 커스터마이징을 통해 어렵지않게 만들었다.

구분
마우스를 올리면
색이 바뀐다
수평으로 길어지면
스크롤도 생긴다

여러모로 블로그 개편 중 가장 빡세고 난감했던 부분이였다.

찾다보니 Markdown과 React를 혼용할 수 있는 MDX라는 것도 있던데, 나름 키치했지만, 마크다운은 순수한 마크다운일 때가 더 나을 것 같기도 하고... 뭐 그렇다.

5. 카테고리별, 태그별 리스트 페이지 추가 🔗

기존의 블로그에도 카테고리가 select 형태로 존재하긴 했었다. 태그의 경우 아예 관련 페이지가 존재하지도 않았었고. 처음 만들 당시에만 해도 NextJS에 대한 기능을 환전히 이해하지 못 해서, 동적 라우팅이 있는지조차 몰랐었다.

카테고리

카테고리와 태그별로 해당하는 리스트를 보여주는 페이지를 동적 라우팅으로 만들었다. 카테고리의 경우 기존의 select 방식이 그닥 이쁘지 않은 것 같아서 카드뷰 형식으로 간단하게 만들었는데, 그렇게 이쁘단 느낌은 안 드는 것 같다. 우선은 마땅한 디자인이 구상되지 않아서 냅둘 생각이다.

  • /posts/category/{카테고리}/1
  • /posts/tags/{태그}/1

URL은 위와 같이 정의했다.

6. RESTful URL 적용 🔗

기존의 URL은 아래와 같이 적용되어 있었다.

  • /posts/?page=1&category=all: 전체 카테고리의 게시글 리스트 1 페이지
  • /posts/brand-new/: 현재 게시글 URL

게시글 URL은 그렇다 치고, 게시글 리스트는 정말 꼴보기 싫은 형태다.

NextJS는 동적 라우팅을 지원한다. TypeScript 기준으로 [page].tsx, [...page].tsx와 같이 생성하면 된다. 이름의 page는 단순히 할당되는 라우팅 변수명을 의미하므로, 어떤 것이 와도 상관없다. [...page].tsx와 같은 형태는 동적 변수가 여러개가 올 경우 사용한다.

블로그 개편하기 이전에 적용하긴 했지만, 게시글 URL에도 약간의 변화를 줬다. 이는 기존의 Jekyll 블로그를 사용하면서 겪었던 경험을 토대로 개선한건데, Jekyll의 경우 블로그 제목을 yyyy-MM-dd-title.md와 같은 형태로 관리한다. 즉, 날짜정보를 제목에서 관리하는데, 이게 또 게시글을 시간순으로 정렬하기 편해서 보기가 쉬웠다. 또한 요즘 블로그 보니 /yyyy/MM/title과 같이 URL을 구성하던데, URL에 덩그러니 제목만 있는 것보다 이쪽이 뭔가 더 이뻐보여서 이렇게 구성하기로 했다.

굳이 /yyyy/MM/dd/title 형식으로 일자까지 포함시킨 이유는, NextJS 특성 상 URL 정보만으로 완전한 마크다운 파일 이름을 역으로 만들 수 있어야 라우팅하기 편하기 때문.

최종적으로 변경된 URL은 아래와 같다.

  • /posts/1: 전체 카테고리의 게시글 리스트 1 페이지
  • /posts/JAVA/2: JAVA 카테고리의 게시글 리스트 2 페이지
  • /posts/2021/07/26/brand-new: 현재 게시글

URL 끝에 /가 붙는데, NextJS에서 trailing slash 옵션을 켰기 때문이다. 해당 옵션을 키면 해당 페이지를 항상 index.html로 만들어준다. 예를 들어, /pages/posts.tsx가 있다면 옵션 여부에 따라 아래와 같이 변환된다.

  • /posts.html: 옵션을 끌 경우
  • /posts/index.html: 옵션을 킬 경우

톰캣에서 개발 서버를 테스트할 때, 반드시 .html을 붙여줘야 하길래, 해당 옵션을 켰었는데, GitHub는 .html 확장자를 생략 가능해서 굳이 안 해줘도 상관없었다.

끝에 index.html이 있을 경우 /posts/로 접속하기 때문에 구글 서치엔진의 URL이 꼬여버렸다... 새로 갱신은 해놨는데, 구글 서치엔진 반영이 워낙 느려서 당분간은 유입이 잘 안 될 것 같다.

7. 기타 UI 개선 🔗

그 밖에 크고 작은 UI를 개선했다. 가장 큰 변경점은 Bottom Nav 삭제.

모바일의 브라우저는 상단의 주소창과 하단의 메뉴가 나타났다 안 나타났다 하는데, 이 과정에서 페이지의 높이가 바뀌어버린다. 이에 따라서 Bottom Nav의 위치가 수시로 왔다갔다해서 UX를 너무 해친다.

Bottom Nav를 없엔 대신 슬라이드 메뉴를 추가했는데, 이게 그리 이쁘지 않아서 적절하게 디자인을 다시 해 줄 생각이다.

게시물 아이템

또한 기존의 게시글 아이템도 좀 더 카드뷰스럽게 만들었고, 사진을 좀 더 강조했다. 또한 태그같은 난잡한 부분은 슬라이드 메뉴 형태로 깔끔하게 정리했다.

여담 🔗

근 며칠 간 블로그 개편 작업에 몰두해서 진행 중이던 백준 알고리즘 풀이가 홀딩됐다. 1020번을 풀었는데, 중간 정도 풀이만 써놓고 아직 마무리하질 못 했다. 이거 풀이도 까먹을 지경인데...

아직 완전히 완성된 건 아니지만, 어느정도 정리가 된 것 같으니, 그동안 멈춘 작업을 다시 진행해야겠다.