블로그 만들기

왜 블로그를 만들었나? 도메인, 글쓰기, Emacs, NextJS 경험기


직접 컨텐츠 생산을 해보고자 rsl.kr 를 만들었습니다. 이메일 주소 r@rsl.kr 도 만들고 싶었고요. 짧은 도메인을 몇 개 경험해보고 TimFeriss Show 의 Derek Sivers 에피소드를 보고 난 다음 그런 마음이 들었습니다. Derek Sivers 가 자신은 도메인 (sive.rs) 을 유지하고 있고 이런 디테일을 좋아하는 분들이 있다 말하는 것을 들었습니다. 그런 사람이 되고 싶었습니다.

Ghost, velog, medium, dev.to, WordPress, ... 사이에서 고민하다 Next.JS 와 Headless CMS 로 고르자 싶었습니다. Next.JS 에 많이 노출되기도 했고 -- Vecel 의 기세가 무섭습니다 -- React 기반 HTML 생성을 맛보고 싶은 마음 때문이었습니다. Next.JS 문서가 워낙 훌륭했기 때문이기도 합니다.

React 를 거의 모르지만 Next.JS 문서를 읽으니 React 의 Suspense 와 Hook 기능을 저렇게 부드럽게 녹일 수 있구나 하며 감탄했습니다. File Based Routing 이 신기했고 Rails 를 처음 봤을 때의 놀라움이 다시 느껴졌습니다. DHH 가 Rails 를 만들고 10 년쯤 지났을 때 그러니까 지금으로 부터 10년 전 당신이 Rails 를 만들지 10년이 지났는데 어떤 면에서 거의 변하지 않았고, 가장 업계에 큰 영향을 준 것이 Convention over Configuration 이라 하셨습니다. Next.JS 에서 그 접근이 아주 잘 구현되었다 싶습니다. React Server Component 가 도입되면서 일반 GET POST Handler 를 NodeJS 에서 직접 저렇게 구현할 수 있다는 것도 신기했고요. Server Actions 라고 부르는 듯 보였습니다.

Next.JS 를 선택 후 Next.JS 안에서 어떻게 구현할까? 로 넘어갔습니다. HTML, CSS 도 배워야 하니 TailwindCSS 와 Markdown 을 골랐습니다. Markdown 을 직접 HTML 로 바꾸거나 Headless CMS 에서 가져와 보여주고자 했는데 Next.JS 홈페이지에 Template 이라는 섹션이 있고 거기에 제가 찾던 Blog Stater Template 이 있었습니다. 설명을 보니 라이브러리 Contentlayer 를 써서 Markdown 이나 다른 Headless CMS 까지 Source 로 사용할 수 있다 했습니다. 실행해보니 잘 돌아갔습니다. Markdown 만 써도 돌리는 중입니다.

Pulumi 로 했던 AWS EKS 구성을 글감으로 삼았습니다. 글쓰기도 관건이지만 Pulumi Typescript 코드를 Blog 안에 정확히 옮기고 싶었습니다. Pulumi Typesciprt 코드를 예제만을 위해 가지고 있던 코드에서 정리하며 Markdown 에 소스코드를 붙여 갔습니다. 가지고 있던 Typescript Pulumi 코드에는 Multi Project 를 위한 Loop 나 Pulumi Stack 을 쓰기 위한 설정, 직접 만든 Pulumi Component 가 있었습니다. 글을 복잡하게 만드는 요소이자 처음 접할 때는 불필요하다 보고 이런 요소없이 평평하게 다듬어 가며 글을 적었습니다.

글쓰기에 시간이 너무 오래 걸렸고 궁여지책이지만 말로 설명한다고 생각하면 솜씨는 더 부족하지만 조금 더 글을 쓸 수 있었기에 OSX 의 Dictation 기능을 켜고 받아쓰기로 글을 입력을 해 갔습니다. Dictation 결과가 이상할 때도 잘 할 때도 있는데 여튼 그렇게 초벌을 입력하고, 처음 말한 저조차 알 수 없는 단어들은 해독, 재구성하며 글에 살을 붙였습니다. 1

초벌 작업을 모두 끝내고 예제를 위해 코드를 많이 바꿨으니, Markdown 코드 블럭 안 코드를 바탕으로 다시 새로운 Pulumi Project 를 만들고 index.ts 에 붙여넣기 하며 잘 돌아가는지 검사했습니다. 안 돌아가는 코드가 많아 index.ts 에서 수정을 여러번 했습니다. 수정하면서 Markdown 주석에는 ID 를 붙이고 Typescript 에도 그 ID 가 달린 마커를 붙였습니다. TypeScript 안 마커는 "// START - <ID>", "// END" 로 했습니다.

EKS 를 정의한 부분이라면 Markdown 주석을 [//] # (EKS) 붙이고 index.ts 에는 // START - EKS // END 로 ts code 앞뒤에 마킹을 한 것이죠. 이렇게 해두고 ts 코드를 Markdown 으로 복사해 넣는 기능을 Elisp 을 이용해 구현했습니다.

(defun rsl-extract-snippet ()
  (interactive)
  (let ((s (extract-from-snippet (find-id))))
    (clear-next-markdown-code)
    (insert s)))

Markdown 주석 [//] # (EKS) 이 있는 줄에서 rsl-extract-snippet 을 실행시키면 잘 실행된 index.ts 안에서 Marker EKS 가 달린코드를 찾아 Markdown 코드 블럭을 덮어쓰도록 만들었습니다. 각 함수를 따로 설명해봅니다. 2

find-id 함수는 현재 있는 줄에서 ID 를 찾아 리턴합니다. 현재 줄 괄호안 문자열을 찾도록 정규표현식 "((\w))" 를 썼습니다.

(defun find-id ()
  (let* ((line (thing-at-point 'line t))
         (id (when (string-match "(\\(\\w+\\))" line)
               (match-string 1 line))))
    id))

index.ts 에서 ID 로 마킹된 부분을 찾아 리턴하도록 extract-from-snippet 을 만들었습니다.

(defun extract-from-snippet (id)
  (with-current-buffer snippet-buffer
    (goto-char (point-min))
    (re-search-forward (concat "// START - " id))
    (forward-line 1)
    (let* ((s (point))
           (_ (progn
                (re-search-forward "// END")
                (beginning-of-line)))
           (e (point)))
      (buffer-substring s e))))

Markdown 코드블럭 안 쪽을 지우도록 하기도 했습니다.

(defun clear-next-markdown-code ()
  (interactive)
  (let ((s (progn
             (search-forward "```")
             (forward-line 1)
             (point)))
        (e (progn
             (search-forward "```")
             (forward-line -1)
             (end-of-line)
             (forward-char 1)
             (point))))
    (delete-region s e)))

이렇게 초벌 글쓰기를 끝냈습니다.

그리고 어색한 문장을 다듬기 시작했습니다. '것이', '줍니다', '피동형 문장' 을 대부분 제거했습니다. '구성하다', '만들다', '조합하다', '코딩하다' 중에 특히 많이 반복되는 단어들도 바꿔가며 쓰도록 이리저리 고쳤습니다. 좋은 자료도 각주로 넣기 위해 노력했습니다. 이렇게 다듬는 시간이 생각보다 즐거웠습니다. 결과물이 정말 괜찮은지에 대해서는 잘 모르겠습니다만 여튼 만족스러웠습니다.

초벌 쓰기는 RoamResearch 에서 했었고 이것을 Flat Markdown 으로 Export 해서 Emacs 에 넣고 고쳐보기 시작했습니다. 작업을 하다보니 olivetti-mode 로 바꿔서 보면 무척 쾌적해 져서 이렇게 바꿔 놓고 쓰기 시작했습니다. 확실히 다른 요소가 사라지고 좀 더 집중되는 맛이 있었습니다. 3

또 글을 쓰다 보니 좀 더 잘 쓰고 싶은 마음에 도서관에서 책을 살펴보다가 "네 번째 원고, 존 맥피"4 를 읽게 되었습니다. 퓰리쳐상을 수상한 논픽션 작가로 오랫동안 뉴요커에 글을 내신 분입니다. 그 책을 보면서 문장을 얼마나 성심성의껏 쓰고 또 편집자, 교열자, 팩트체커 하시는 분들이 노력을 기울이는지 좀 더 알게 되었는데요. 그 때 하나의 단어를 찾기 위해서 유의어 사전 뿐만 아니라 일반 사전을 그대로 읽으면서 적절한 단어를 찾기도 한다는 말을 보았습니다. 읽을 때마다 영 어색한 부분이 있다 싶었는데 그런 부분들에서 직접 단어를 찾다 보니 정말 조금 나아지는 듯 했습니다. 이 때도 Emacs 안 환경에서 단어위에 커서를 두고 키 하나만 누르면 "네이버 국어사전" 웹페이지가 나오도록 이렇게 설정해서 썼습니다. 5 이렇게 찾기가 쉬워지니 더 많이 사전을 보고 고치게 되었습니다. '연결하다' 라는 단어를 너무 많이 쓰다가 '잇는다' 라는 표현을 섞었는데 모두 사전 덕택입니다.

(setq +lookup-provider-url-alist
      '(("Google" +lookup--online-backend-google "https://google.com/search?q=%s")
        ("NaverKoDic" "https://ko.dict.naver.com/#/search?query=%s")))

글 "Pulumi 에서 EKS 사용하기" 각주를 많이 넣은 글이었고, 각주 때문에 Next.JS 템플릿에서 사용하고 있던 Contntlayer 설정을 고쳐줘야 했습니다. Contentlayer 에서 고치는 것이 맞는지 mdx 란 무엇인지, 제가 사용하고 있던 Next.JS 템플릿에서 Contentlayer 를 호출하고 있는 부분을 찾아보기는 했습니다. 그다지 도움이 안 되었습니다. 그러다 Contentlayer 의 GitHub Issue 페이지에서 힌트를 찾고 그 단어 (remarkGfm) 도 더 검색해 나갔습니다. 결국 찾을 수 있었는데, 처음 검색을 시작할 때 Contentlayer 가 워낙 다양한 Source 를 지원하고 있으니 Markdown 에 있는 각주는 지원안 할 수도 있다 했던 것은 저의 큰 착각이었다 싶습니다. contentlayer.config.js 의 makeSource 함수에 옵션을 적절히 넣는 것만으로 가능합니다.

export default makeSource({
  contentDirPath: "./content",
  documentTypes: [Post, Page],
  mdx: {
    remarkPlugins: [remarkGfm],
  }
})

배포는 별 고민없이 fly.io 에서 해보려고 했습니다. 무료이기도 했고 워낙 서비스를 잘 한다고 느꼈던 터라 또 사용해 보고 싶었습니다. 그래도 HelloWorld 정도지만 6개월 전에 그래도 한 번 써보았다는 점도 있었습니다. fly.io 에서 Next.JS 는 어떻게 배포하는지 찾아보기 시작했습니다. 따로 Next.JS 용 배포 문서는 찾지 못했지만 Javascript App 을 배포하는 법 페이지를 보니 npm run start 로 serving 이 되도록 만들어 두고 fly launch fly deploy 를 실행하면 된다고 했습니다. 정말 별 문제없이 바로 잘 떴습니다. 그래서 여러분이 지금 보고 계실 페이지는 fly.io 의 Singapore Edge 에서 서빙되고 있는 중입니다.

이렇게 블로그에 올릴 글을 만들었습니다.

Footnotes

  1. 나중에는 OSX 의 받아쓰기가 아니라 구글 독스의 음성입력 기능을 사용했습니다. https://rsl.kr/posts/writing-with-voice

  2. 어떻게 보면 이 코드를 기록으로 남기고 싶었군요. 비슷한 일을 2년에 한 번 꼴로 하는데 늘 바닥부터 하게 되서 기반을 남겨두고 싶었습니다.

  3. distraction-free, zen mode, writeroom 같은 키워드로 검색하면 비슷한 서비스들이 많습니다.

  4. 네 번째 원고, 존 맥피 https://www.yes24.com/Product/Goods/90004382

  5. 제가 쓰는 환경은 Doom Emacs 라서 제가 직접 설정하지는 않았지만 부가기능이 많고 이 웹페이지를 여는 작업도 +lookup-provider-url-alist 를 고쳐주는 것만으로 끝났습니다.