2021 블로그 리뉴얼 완료
Written in 2021/12/04 11:26:54 UTC, categoried as web
Motivation
블로그를 새로 만들자. 블로그의 성능과 사용성을 점검한 뒤 내린 결정이었다. 당시 블로그 백엔드는 Node.js
로 작성해 docker
로 배포하고 있었다. 메모리 사용량을 분석해보니 상시 100MiB
정도 먹는다는 충격적인 결과가 나왔다. 뭔가 열심히 돌고 있는 것도 아니었다. 나는 적잖이 놀랐다. 심지어 이게 전부가 아니었다! 블로그 검색 기능을 위해 나는 elasticsearch
노드를 직접 운영하고 있었는데, 이 녀석은 메모리를 최소 2GiB
이상 요구한다. 거기에 데이터베이스까지. 다달이 결제되는 AWS
비용이 9만원씩이나 하는 이유가 있었다. 그래서 블로그의 점진적 개선을 위한 작업을 계획했다. 먼저 백엔드를 Go
로 다시 작성하기로 했다. Go
가 요즘 대세니까.
Go
Go
를 처음 접한 내 감상은, 괜찮은 언어지 않나? 일단 C
와 비슷해서 문법도 쉽고 간단하고, C++
의 RTTI
랑 비슷한 리플렉션도 지원한다. 무엇보다 유저 레벨 스레드인 goroutine
이 인상적이었다. 언어 레벨에서 제공하는 channel
을 이용하면 동기화도 어렵지 않다. 네트워크 관련 프로그램을 작성하는데는 최적이다. generic
이 없는 것은 매우 아쉽다. 그래도 나름 장단점이 확실한 좋은 언어라 생각한다.
Too verbose
그런데 뭔가 좀 여러모로 장황하다는 느낌을 받았다. 에러 처리가 그 중 하나였다. 아래는 Go
에서 자주 볼 수 있는 에러 처리 루틴이다. Rust
와 비교하면 다음과 같다.
// Go
foo, err := some_function()
if err != nil {
return nil, err;
}
// Rust
let foo = some_function()?;
웹 애플리케이션에서 위와 같은 처리는 매우 흔하다. 결국 비슷한 코드가 군데군데 있는 꼴이 된다. 개인적으로 이게 많이 불편했다. 비교 대상인 Rust
가 훨씬 더 간결하고 우아하다. 또 다른 문제는 generic
이 없어서 그런가 map
이나 filter
같은 유틸리티가 없다는 점이다. 함수를 만들어 재사용하면 되니 커다란 문제는 아니었다.
ORM problem
나는 ORM
을 매우 선호한다. raw SQL
보다 이식성이 뛰어나고 직관적이기 때문이다. 그래서 entgo
를 사용했다. entgo
는 정의된 schema
에 따라 코드를 자동 생성한다. 덕분에 타입 안정성과 직관성에서 큰 장점이 있었다. 하지만 불어나는 코드와 더러워지는 git
히스토리를 보자니 마음이 심란해졌다. schema
관리도 어려웠다. schema
를 수정하려면 코드가 자동 생성 되는 곳 내부에 위치한 폴더를 열어야 한다. 그러면 무수히 많은 파일이 나를 반겨준다. 다른 ORM
을 찾아보았지만 딱히 더 쓸만한 것은 없어보였다.
SSG
몇 개월 전이었다. 다크 테마로 디자인을 바꿀까 생각했다. 하지만 마음에 들만한 디자인을 만들어낼 능력이 내겐 없었다. 그래서 디자인을 참고할 다른 블로그를 돌아다니던 도중, 터미널스러운 디자인의 블로그를 발견했다. 매우 마음에 들었다. 전통적인 MPA
방식에 Javascript
도 없다. 어드민용 페이지도 없고, 블로그 작성기도 당연히 없을 것이다. 완전한 정적 페이지인 그 블로그들을 보고 무언가 열리는 느낌을 받았다. 그 동안 왜 이리 SPA
이니 SSR
이니 하는 것에게 집착했을까. 하지만 그렇다고 여러 이점이 있는 최신 웹 기술을 완전히 포기할 수는 없었다. SPA(Single Page Application)
는 클라이언트에게 부담을 안겨준다. SSR(Server Side Rendering)
은 그 부담을 서버가 대신 받는다. 더 좋은 방법은 없을까? 답은 SSG(Static Site Generator)
다. SSR
을 페이지 빌드 시에 돌린다. 그 결과물을 정적 페이지로 만드는 방식이 바로 SSG
다. SSG
는 SPA
와 관련이 없기 때문에, 여기에 SPA
를 끼얹을 수도 있다. 하지만 Javascript
의 완전한 제거를 위해 그렇게 하지 않았다. 이러면 자연스레 블로그 백엔드도 존재 이유가 사라진다. 하지만 여전히 포스트를 관리할 방법은 필요하다. 파일시스템 어딘가에 포스트를 저장해 놓고 git
같은 걸로 관리하면 되지 않을까?
SvelteKit
나는 나의 선택권이 있는 개인 프로젝트에선 프론트엔드 프레임워크로 항상 Svelte
를 사용한다. 마침 직접 구현한 SSR
도 있고, 가져다 개조해서 쓰면 되겠지. 완전히 오산이었다. Webpack
으로 빌드한 SSR
컴포넌트인데 SSR
시 필수적인 render
함수가 일부 컴포넌트에는 존재하지 않았다. 도무지 원인을 찾을 수 없어서 커뮤니티에 도움을 청해보아도 깜깜무소식이었다. (지금 생각해보면 Webpack
이 아니라 rollup.js
를 쓰는게 맞지 않았나 싶다.) 그러다 SvelteKit
을 써보라는 조언을 받았다. 다행히 사용법은 어렵지 않았다. 기대하던대로 모든게 잘 동작했다. 딱 하나, Javascript
가 생긴다는 것만 빼면.
Elder.js
Elder.js
는 Svelte
용 SSG
프레임워크다. SEO
를 굉장히 강조하고 있다. 커뮤니티에 조언을 구했더니 이걸 쓰라는 말을 들었다. SvelteKit
으로 작성했던 웹 페이지들을 전부 Elder.js
로 포팅했다. 도중에 ES Module
과 CommonJS
의 환장할 콜라보레이션 덕분에 매우매우 해멨다. 다행히 이삼일 정도 날려서 해결했다. Tailwind CSS
관련 문제도 있었다. 역시 시간을 갈아서 해결했다.
결과물이 내가 딱 원하던 그것이라 매우 만족했다. 다만 한 가지 아쉬운 점이 있었다. Elder.js
에서는 모든 permalink
가 /
로 끝난다. 아무 문제가 없는 것처럼 보인다. 하지만 일부 환경에서는 (특히 나같은 AWS S3
+ AWS Cloudfront
를 구성한 입장에서) explicit하게 index.html
까지 붙인 완전한 경로가 필요하다. 하지만 그런 기능은 없다. 편법으로 해결했다. 빌드할 때마다 경고가 줄줄 나지만.
Subfont
터미널스러운 블로그의 핵심 디자인 요소는 무엇일까? 바로 고정폭 서체다. 나 역시 고정폭 서체를 포기할 수 없었다. 그래서 고심한 끝에 NotoSans Mono CJK
를 사용하기로 했다. CJK
인 이상 폰트 용량이 큰 것은 어쩔 수 없지. 그런데 커도 너무 컸다. Regular
와 Bold
딱 두 개인데, 개당 10MiB 정도 한다. 총합 20MiB라는 말도 안 되는 크기를 서빙해야 한다(폰트가 전체 용량의 99%가 넘는다). 시험해보니 역시 전송 시간이 너무 오래 걸렸다. 그래서 Subfont
를 쓰기로 했다. Subfont
는 페이지를 렌더링해본 다음 실제로 사용하는 글리프만 모아서 서브셋을 만들어준다. 사용법이 쉽고 강력하다. (역시 상당한 삽질이 있었지만) 적용하고 나니 개당 10MiB 짜리가 거의 20KiB 정도로 줄어버렸다. 경이로웠다.
Syntax Tree
이제 프론트가 완전히 준비됬다. 포스트만 잘 컴파일해서 html로 생성해주기만 하면 된다. 기존의 marked
를 쓸까 잠깐 고민했다. 하지만 누군가 추천해준 remark
가 생각나 별 생각 없이 이걸 써보기로 했다. 그런데 사용해보니 매우 잘 설계됬다는 걸 느낄 수 있었다.remark
는 unified
프로젝트의 일부로써 모든 것을 ast
형태로 표현한다. 이 ast
를 다른 용도의 ast
로 손쉽게 가공할 수 있다. syntax-tree
에 ast
와 그 유틸리티들이 모여있는데, 규모가 대단하다. 내게 필요한 ast
는 mdast
와 hast
둘 뿐이다. 전자는 markdown
용 ast
고 후자는 html
용 ast
다. 계획은 이렇다.
- 포스트를 읽고
mdast
로 변환한다. mdast
를hast
로 바꾼다.hast
를 가공해 이미지와 비디오를 넣는다.hast
를plain text
와html text
로 serialize한다.
약간 헤메는 감이 없잖아 있었지만, 그래도 잘 해결했다. 이후에 GFM
과 KaTeX
지원을 추가했다.
Deployment
이렇게 만들어진 static
사이트를 배포해야 한다. 어떻게 할까. AWS S3
와 AWS Cloudfront
를 쓰기로 했다. 별 이유는 없었다. 내가 자주 쓰던 스택이라 그런가. 세팅과 자동 배포 작업은 두 시간도 걸리지 않았다. 그마저도 대부분은 ACM
에서 승인 받기를 기다리는 시간이었다. 작업이 모두 끝났다. 새로운 블로그를 확인해 본다. 이쁘고 쾌적하다. 그런데 모바일에서 보면 깨져보인다. 아직 모바일 대응을 안 했으니까. 시간날 때 할 예정이다.
기존의 AWS
에서 호스팅하던 블로그 백엔드 서버는 날려버렸다. 이제 다달이 9만원씩 내지 않아도 된다!
Conclusion
기존 블로그 시스템을 SSG
를 통해 그냥 정적 페이지로 만들어봤다. 귀찮게 백엔드를 작성/유지보수하지 않아도 된다. 포스트도 기존 IDE를 활용해 훨씬 더 편리하게 작성할 수 있다. 디자인도 맘에 들고 이쁘다(남들 눈에도 그런지는 모르겠다). 간만의 성공적인 마무리다.