Outsider's Dev Story

Stay Hungry. Stay Foolish. Don't Be Satisfied.
RetroTech 팟캐스트 44BITS 팟캐스트

블로그를 정적 사이트로 바꿨습니다

이전 글에도 종종 얘기했지만, 코딩 에이전트에 많이 익숙한 편은 아니다. 가장 큰 이유는 예전처럼 집에서 코딩을 많이 하고 있지 않다 보니 코딩 에이전트와 친해질 시간이 별로 없었다. 예전에는 사이드 프로젝트 아이디어가 너무 넘쳐나서 문제였는데 언젠가부터 아이디어가 별로 없거나 아이디어의 규모가 커서 쉽게 시작하지 못하고 있었다. 그렇다고 나도 쓰지 않을 웹서비스 같은 걸 만들고 싶진 않았다.

주변 사람들과 AI 얘기를 하다가 블로그나 만들라는 얘기를 듣고 실제로 필요한 일이기도 했고 요구사항이나 취향도 꽤 명확하므로 내가 하기 딱 좋은 생각이 들었다.

Textcube 블로그

인터넷이 뜨기 시작했던 2000년대 초에는 홈페이지가 유행해서 많은 사람이 홈페이지를 만들었고 그때 나도 홈페이지를 가지고 있었다. 그건 개인 홈페이지였고 2006년쯤에는 블로그가 유행하기 시작해서 나도 테터툴즈라는 도구로 블로그를 만들었다.

블로그를 만들기만 하고 작성할 글이 없어서 방치하다가 2007년 개발자로 일하기 시작하면서 개발 블로그로 운영하기 시작한 게 여기까지 왔다.

블로그 플랫폼은 테터툴즈에서 Textcube로 바뀌었지만 이건 프로젝트의 브랜딩이 바뀌어서 그런거고 난 처음부터 지금까지 같은 플랫폼을 그대로 사용하고 있다. 처음 사용할 때는 꽤 앞서가고 잘 만들어진 플랫폼이었지만 시간이 지나면서 웹 기술도 크게 발전하고 트렌드도 바뀌었기 때문에 낡은 플랫폼이 되어버렸다.

약간 고쳐서 쓰고 있긴 했지만 크게 문제 안 되는 고장 난 기능들은 그냥 방치한 채로 쓰고 있었고 난 PHP 개발자가 아니었기 때문에 대대적인 수정은 하지 못했다. 블로그 플랫폼을 바꾸는 고민도 해봤지만 이미 작성한 글이 너무 많았고, 블로그의 퍼머링크를 망가뜨리고 싶지 않았기 때문에 다른 플랫폼으로 이사가는 건 오래전에 포기한 채로 운영하고 있었다. 글이 많아지면서 성능 문제도 종종 생겨서 몇 년에 한 번씩 튜닝도 하면서 고생하고 있었다.

AI로 새로 만들기

이제 코딩 에이전트가 너무 좋아졌기 때문에 아예 블로그 플랫폼을 새로 만들면 퍼머링크도 유지할 수 있고 앞으로 원하는 대로 수정할 수 있을 거라고 생각해서 새로 만들기로 했다.

블로그라는 건 기술적으로 복잡할 건 없다고 생각했기에 정적 사이트로 만들어서 CDN으로 제공하면 지금보다 성능도 좋아지고 글 개수가 늘어나도 문제가 없을 거라고 생각해서 정적 사이트로 만들기로 결정했다.

지난 2월인가 카카오가 ChatGPT Pro를 너무 싸게 팔아서 나도 그때 구매해서 사용하고 있었고 이 사용량이 나에겐 넘쳐나기 때문에 이때 개인 Claude 구독도 끊은 상태였다.

블로그는 텍스트 큐브를 포함해서 이미 Git으로 관리하고 있었기에 소스코드와 백업해 둔 MySQL 덤프 파일을 한꺼번에 주고 정적 사이트로 만들어달라고 요청했더니 한참 작업한 후에 Astro로 콘텐츠까지 마이그레이션 된 사이트를 던져줬다. 난 좀 차근차근히 하고 싶다는 생각이 들었는데 당시 사람들이 보통 말하던 Codex는 한 번에 작업을 많이 한다는 선입견이 있었던 편이라 결과물이 맘에 들지 않아서 Claude Pro를 일단 결제해서 작업을 하기 시작했다.(지나서 생각하면 내가 지시를 좀 애매하게 해서 그런거 였고 Codex로 계속 작업을 했더라도 괜찮았을 거라고 생각한다.)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
개발 관련 블로그를 오랫동안 운영하고 있습니다.

이 블로그는 Textcube라는 아주 오래된 PHP 블로그 플랫폼을 사용중이고 PHP와 MySQL을 이용해서 운영하고 있습니다.

하지만 Textcube가 더이상 유지보수 안되는 프로젝트이고 운영할때 서버도 필요하기 때문에 관리하기 더 쉬운 정적 사이트 형태로 변환하려고 합니다. 혼자서만 사용하는 블로그이므로 일단 글을 작성하고 나면 수정할 일이 별로 없고 나중에 작성 기능을 따로 만들면 되기 때문에 정적 사이트가 훨씬 좋다고 판단했습니다.

그동안 따로 블로그를 다른 플랫폼으로 옮기지 못했던 이유는 기존의 글이 1700개 쯤 있으므로 각 글에 대한 퍼머링크(숫자 slug 사용)를 그대로 유지하는 것이 제일 중요했기 때문입니다.

* 기존 블로그 플랫폼의 소스 코드는 blog 디렉토리에 있습니다.
* blog.sql 파일은 운영중인 MySQL의 덤프 파일입니다.
* 기존 글의 퍼머링크를 반드시 유지해야 합니다.(각 글에 대한 정보는 blog.sql을 참고 해야 합니다.)
* 디비에는 댓글 기능도 있는데 기존에 남겨진 댓글만 있고 새로운 댓글은 달리지 않습니다. 그래서 댓글 기능을 만들 필요는 없지만 기존에 남겨진 댓글은 보존 차원에서 댓글로 표시되었으면 합니다. 블로그 댓글 기능은 외부 플랫폼을 이용할 건데 일단 중요하게 생각하지 않아도 됩니다. 
* 글은 아주 오래된 글은 마크다운으로 되어 있지 않을거지만 대부분의 글은 마크다운으로 되어 있습니다. 그래서 마이그레이션을 포함해서 이후 새로운 글도 마크다운으로 관리되었음 합니다. 마크다운이지만 메타태그도 넣고 하려고 MDX가 괜찮지 않을까 합니다. 
* 아마 기본 형태는 블로그 글인 마크다운 파일들을 모아놓은 파일들이 있고 사이트도 기능을 넣고 관리해야 하므로 블로그 템플릿은 관리하는 곳이 분리되어 있어서 글을 추가하거나 삭제하면 빌드해서 정적 사이트 블로그로 만드는게 좋을거 같습니다.
* 현재 사용중인 스킨은 blog/skin/blog/anti_verbose 아래에 있습니다.

현재 생각나는 요구사항은 이정도인데 작업하기 전에 어떤 접근방법이 좋은지 같이 얘기해보면 결정하면 좋겠고 결정된 내용은 설계 파일에 저장하고 이후 참조할 수 있도록 하면 좋겠습니다.

기존 소스코드를 분석해 주었다. 퍼머링크 등은 소스코드만 주어서 그런지 제대로 판단하지 못했지만 상당히 잘 파악해 주었다.

그리고 Codex와 마찬가지로 Astro를 추천해 주고 마이그레션 전략과 폴더 구조, MDX 파일 구조 등을 제안해 주었다. Astro도 써보고 싶긴 했지만 특정 프레임워크에 묶이고 싶진 않았고 프레임워크에 의존할 정도로 복잡하진 않을 거라고 생각했고, AI가 충분히 잘할 거라고 생각했기에 이왕 기존 플랫폼에서 벗어나는 김에 직접 구현하기로 하고 싶었다. Claude는 Node.js를 제안했지만 어차피 빌드해서 HTML로 나올 것이므로 도구 자체가 Node.js일 필요는 없다고 생각해서 언어를 제한하지 말라고 했다.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
좋은 지적입니다. 빌드 도구의 언어를 자유롭게 선택할 수 있으니 비교해 보겠습니다.

빌드 도구 언어 후보

1. Rust
- 성능: 최고. 1,636개 파일 전체 빌드도 1~3초 수준
- 마크다운: pulldown-cmark (매우 빠르고 안정적)
- MDX: mdxjs-rs (MDX 팀이 직접 만든 Rust 구현체, 다만 JS 쪽보다 성숙도 낮음)
- 인크리멘탈 빌드: 파일 해시 기반 캐싱 직접 구현 용이
- 단점: MDX 생태계가 JS에 비해 얇음. 빌드 도구 자체 개발 비용이 높은 편

2. Go
- 성능: 매우 빠름. Rust에 근접
- 마크다운: goldmark (Hugo가 사용하는 파서, 매우 빠르고 안정적)
- MDX: 네이티브 지원 없음. 직접 구현하거나 마크다운만 사용해야 함
- 인크리멘탈 빌드: 구현 용이
- 단점: MDX를 쓰려면 별도 처리 필요

3. Bun
- 성능: Node.js 대비 35배 빠름. 전체 빌드 510초 수준 예상
- 마크다운/MDX: unified/remark/rehype 생태계 그대로 사용 가능 (가장 성숙한 MDX 구현)
- 인크리멘탈 빌드: 파일 와칭 + 해시 기반으로 구현
- 단점: Rust/Go 대비는 느림. Bun 자체가 아직 성숙 중

4. Node.js/TypeScript
- 성능: 가장 느림. 전체 빌드 15~30초 예상
- 마크다운/MDX: 생태계 최고
- 단점: 1,636개 파일에서 체감되는 느림

핵심 분기점: MDX가 정말 필요한가?
MDX의 핵심 가치는 마크다운 안에서 JSX 컴포넌트를 사용하는 것인데, 프레임워크 없는 정적 사이트에서는 그 이점이 크게 줄어듭니다.

만약 마크다운 + frontmatter로 충분하다면:
- Go나 Rust를 자유롭게 선택 가능
- 성능이 크게 좋아짐
- 커스텀 문법이 필요하면 remark 플러그인 대신 마크다운 파서의 확장 기능으로 처리

만약 MDX가 꼭 필요하다면:
- Bun이 현실적 최선 (JS 생태계 + 성능 타협점)

제 추천
Go + goldmark 조합이 가장 균형 잡힌 선택이라고 생각합니다.
- 1,636개 파일 전체 빌드가 1~3초면 인크리멘탈 빌드의 필요성 자체가 줄어듦
- goldmark은 Hugo가 검증한 파서로 확장성도 좋음
- 단일 바이너리로 빌드 도구 배포 가능
- Go의 text/template이 HTML 템플릿으로 충분
- 동시성 처리가 간단 (goroutine으로 파일 병렬 처리)

Rust도 좋지만 개발 속도 면에서 Go가 더 실용적이고, 이 규모에서 Rust와 Go의 성능 차이는 체감하기 어렵습니다.

MDX 대신 마크다운 + frontmatter + goldmark 확장으로 가는 방향은 어떻게 생각하시나요?

Go 언어를 추천해 줘서 난 Go를 할 줄 모르지만, 이 제안을 받았다. 참고로 이 작업을 할 때부터 난 코드를 읽을 생각이 없었다. 업무에서는 코드 봐야 된다는 입장이지만 이건 개인 프로젝트라서 위험성도 없고 코딩 에이전트로 어느정도할 수 있는지도 직접 경험해 보고 싶었다. 그래서 이 작업은 터미널이 아니라 Claude 데스크톱 앱으로 작업했다. 물론 간단한 건 내가 직접 수정하거나 터미널에서 커밋했다.

잘못 파악한 부분을 지적해 주고 합의한 내용으로 디자인 파일을 만든 뒤에 본격적으로 작업을 시켰다. go로 블로그 시스템을 만들고, 이어서 SQL 파일에서 마이그레이션도 시켰다. 기존 코드가 있어서 기대보다 분석을 잘해주었지만 의도를 잘 모르는 부분은 알려줬다. 예를 들어 Textcube에도 댓글 기능이 있기 때문에 십여 년간 쌓인 댓글이 있고 몇 년 전부터는 utterances로 댓글 시스템을 교체했다.(그리고 고장 나서 그런지 Textcube 댓글은 더 이상 달리지 않았다.) 이런 경우 정적 사이트에서는 utterances만 쓸 계획이지만 기존에 남겨진 댓글을 지우고 싶지 않았기 때문에 이런 배경을 알려주고 기존 댓글도 마이그레이션 하고 고정하도록 했다.

내가 아직 AI를 잘 쓴다고 생각하고 있진 않았기에 모델에 의존하고자 Opus 4.7 Extra high로 작업했다.(처음에는 4.6이었고 중간부터 4.7로 바꾸었던 걸로 기억한다.)

Claude Pro 플랜은 리밋이 꽤 빡빡하므로 작업하다가 리밋 걸리면 놔두었다가 리밋 풀리면 이어서 작업하게 시켰다. 내가 계속 이거만 보고 있을 수는 없다 보니 처음에는 Pro 플랜으로 이렇게만 해도 충분했다. 작업하면서는 계속 md 파일에 계획이나 규칙 등을 업데이트했고 커밋할 때마다 작업 로그도 남기게 했고 테스트 등 여러 검증 도구도 좀 늘려가면서 커밋할 때 검증하고 커밋하도록 규칙에 추가했다.

블로그가 복잡하진 않지만, 이 정도 가이드만 했는데도 꽤 똑같이 만들어주어서 시작부터 좀 놀라긴 했다. 기존에도 스킨 파일이 HTML로 있었기 때문에 꽤 쉽게 모양을 맞춘 것으로 보인다.

전체적인 레이아웃을 맞추기 위해서 기존 skin.html과 라이브로 떠 있는 블로그와 비교를 시키면서 필요한 작업을 파악하기 시작했다.

오래 운영한 블로그라 대부분의 구성 요소는 알고 있기에 하나씩 보면서 추가해야 할 작업을 시키면서 레이아웃을 어느 정도 맞추었다.

1
2
3
참고로 blog 폴더와 blog.sql 파일은 현재 블로그이고 이를 정적 사이트로
마이그레이션 하는 중이므로 작업이 끝나면 blog 폴더 등은 삭제할 예정입니다. 그러므로 이미지나 이런걸 blog 폴더의 아래에 있는걸 
그대로 참조해서 사용하거나 하면 안됩니다. 그러고 있나요?

중간중간 생각나는 실수할 여지가 있는 부분은 체크하면서 규칙에 추가했고, 가이드를 충분하게 주지 않아서 Git으로 관리해야 하는데 추가 안 한 파일 등은 체크하고 규칙을 변경해 가면서 작업했다.

콘텐츠 마이그레이션

스킨은 비교적 간단하지만, 블로그에서 제일 중요한 건 콘텐츠이기 때문에 콘텐츠가 제대로 마이그레이션 되었는지 확인하는 게 중요했다.

콘텐츠 비교를 직접 시키면 토큰이 감당하지 못할 거라고 판단해서 라이브로 운영되는 블로그와 로컬의 블로그 글 링크를 추출해서 유사도를 분석하는 스크립트를 작성하게 했다.

1
2
3
4
5
6
7
스타일을 개선하고 블로그 시스템을 개편하기 전에 정적 마크다운으로 
제대로 마이그레이션 되었는지 확인하고 싶습니다. 
지금 마이그레이션된 글과 https://blog.outsider.ne.kr 에서 
같은 글 번호(퍼머링크)가 내용이 똑같은지 확인하고 싶습니다. 
이는 스타일과는 상관없이 컨텐츠에 대한 얘기입니다. 
이를 모두 LLM으로 하기 보다는 비교하는 스크립트를 작성하고 
그 결과로 대응하는게 좋다고 생각하는데 어떤가요?

작성한 스크립트를 어느 정도 개선한 후에 전수 조사를 시키고 오류를 뽑아내게 했다. 글이 1,600개라서 꽤 어려울 거라고 생각했는데 기대 이상으로 문제를 잘 찾아내고 Major, Minor 이슈를 구분해서 알려주었다.

여기서부터 AI보다 내가 할 일이 많아지기 시작했는데 문제라고 알려주면 글 링크를 열어서 실제로 이게 문제인지 아닌지를 확인하고 어떻게 고쳐야 할지 알려줘야 하므로 내가 확인하는 시간이 오래 걸리기 시작했다.

또한 Textcube에서 처음에는 WYSIWYG 에디터로 글을 작성했기 때문에 900여 개의 글까지는 저장된 콘텐츠가 HTML로 되어 있고 그 이후부터는 마크다운에 익숙해졌기 때문에 마크다운 플러그인을 붙이고 수정해서 그다음부터는 마크다운으로 글을 작성하기 시작했다. 마이그레이션은 모두 마크다운으로 했으므로 Markdown -> Markdown은 비교적 괜찮았지만, HTML -> Markdown에서는 아무래도 오류가 많이 발견되었다. 그래도 이런 부분도 Claude가 알아서 잘 파악하고 frontmatter에 convertedFromHTML를 표시해 주어서 마이그레이션 예외 케이스도 쉽게 처리할 수 있었다.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
---
id: 937
title: "Jade에 HTML을 함께 사용하기"
slug: "937"
date: 2013-05-11T00:27:34+09:00
category: "node.js"
tags:
  - "HTML"
  - "jade"
  - "view template engine"
convertedFromHTML: true
---

Textcube에는 [##_ATTACH_PATH_##][##_page_title_##]처럼 플랫폼이 값을 바꿔치기하기 위한 플레이스 홀더가 있는데 디비에서 마이그레이션하다 보니 이런 내용이 많이 포함되어 있어서 찾아낼 때마다 수정하도록 요청했다.

1
2
3
4
5
6
7
8
9
알려준 이슈 중에 일부만 먼저 보겠습니다. 

967은 이제 정상이므로 결함에서 제외해도 됩니다. 

126, 155번도 앞에서 해소되어 무시해도 됩니다. 

386은 Flickr.com, Gucci.com, twitter.com 부분은 원래 테이블이어야 하는데 텍스트로 되어 있어서 이상하게 표시됩니다. 테이블로 바꾸어 주세요. 

424번 글의 경우 HTML에서 컨버팅 된 글이라서 h1, h2, h3 등이 제대로 처리되어 있지 않습니다. 일부 제목 텍스트가 <font style="font-weight: bold;" size="5">처럼 되어 있는데  이런 글은 제목을 의도한 것이므로 사이즈를 보고 절적한 h 태그로 치환해 주세요. 이건 424뿐만 아니라 convertedFromHTML가 true인 모든 글에 해당합니다.

이런 식으로 Claude가 찾아낸 이슈를 받은 다음에 콘텐츠를 열어서 확인해 보고 이슈는 수정하게 하고 이슈가 아닌 건 넘어가게 하는 작업의 반복이었다.

내가 확인해야 다음 작업을 할 수 있으니 어느 순간부터는 AI보다 내가 더 오래 일하는 거처럼 느껴졌다.

사이트 개선

콘텐츠가 기본적인 부분은 잘 마이그레이션 되었다고 느껴서 스킨을 포함해서 전체적인 블로그를 개선하기 시작했다. 퇴근하고 틈틈이 작업을 하고 있었는데 마침 5월 초에 5일 연속 연휴가 있어서 이때는 작업도 많이 할 수 있어서 Cladue를 Max 5x로 올렸다. 중간중간 리밋이 걸리긴 했지만, 5x가 되니 훨씬 쾌적하게 작업할 수 있었다.

기존에 MySQL 덤프에서 마크다운으로 마이그레이션 하게 했더니 항상 SQL 덤프가 있을 거라는 가정하에 마크다운을 출력 파일로만 쓰고 있어서 이 부분을 정리하게 시켰다. 마이그레이션이 끝난 뒤에는 어차피 SQL 파일은 폐기하고 마크다운 파일로만 관리해야 하니 마이그레이션 관련 코드는 제거했다.

1
2
3
4
네 마이그레이션을 좀더 확인해야하지만 마이그레이션이 완료되면 
git/md 기반으로 전환할 예정입니다. 
깃에 추가해주시고 마이그레이션 도구는 그대로 놔두세요. 
이후에 이슈가 발견될때마다 마이그레이션을 해서 posts를 업데이트해야 합니다.

사이드바 메뉴에 카테고리를 표시하고 카테고리의 글 목록, 태그의 글 목록, 아카이브 페이지, 페이지네이션 등의 기능이 동작하도록 수정하고 글을 보면서 스타일이 맘에 들지 않거나 잘못된 부분을 발견되는 대로 정리했다.

코드 블록도 기존에는 서버에서 <code><pre> 형태의 HTML을 출력하고 클라이언트에서 Prism.js로 신택스 하일라이트를 하고 있었는데 새로 만들면서 Claude가 알아서 goldmarkchroma로 빌드 단계에서 신택스 하이라이트를 위한 구조를 만들고 클라이언트에서는 스타일 정도만 입히는 걸로 바뀌었다. Prism.js도 이젠 유지보수가 잘되지 않아서 잘 정리되었다고 생각한다. 그래도 라이브러리는 별도로 지시하지 않으면 최신 버전을 잘 못 찾는 느낌이라 따로 확인해서 업데이트하게 했다.(GitHub Actions의 각 의존성 버전도 낮게 세팅해 주어서 나중에 다 업데이트했다.)

코드 영역은 HTML과 스타일이 복잡한 편이라서 한 번에 잘하진 못했지만 잘 안된 부분을 설명하거나 그래도 잘 못하면 어떤 HTML이나 스타일이 문제 되었는지 알려주면 사이트를 열어서 확인하고 잘 해결해 주었다. 코드 영역의 가로 스크롤을 줄이기 위해 마우스커서를 올리면 width를 확대하는 기능이 있었는데 이건 아무래도 hover 이벤트를 발생시켜야 확인할 수 있어서인지 이 부분은 잘 해내지 못해서 여러 번 핑퐁 끝에 버그를 잡았다.

아까 말한 대로 오랫동안 운영하면서 콘텐츠의 형태가 다양했기 때문에 코드 블록을 표시하는 방법도 여러 가지 있어서 일부 코드는 제대로 렌더링 되지 않는 부분이 많이 있었는데 발견할 때마다 하나씩 수정했다. 이번에 작업하면서 알게 되었는데 HTML에서 마크다운으로 콘텐츠 작성을 전환하면서 기존 글에서도 코드 영역이 제대로 나오지 않은 부분이 꽤 있었는데 이번에 정리하면서 100%는 아니어도 기존보다는 더 잘 나오게 되었고 코드 블록이 특정 라인부터 시작되는 기능 등은 라이브러리에 의존하고 있었는데 직접 구현시켜서 원하는 대로 쓸 수 있었다. 이번에 개선하면서 코드 블록 복사 기능도 추가했다.

정적 사이트라서 404 페이지도 따로 만들었다. 404 페이지도 좀 재밌게 만들고 있었는데 딱히 생각나는 아이디어가 없어서 그냥 심플하게 만들도록 했다.

1
2
3
4
5
6
404 페이지를 구현하는데 지금 블로그 스킨은 그대로 유지합니다. 
즉 상단에 Outsider Dev story라고 써있는 헤더는 그래도 유지하고 
햄버거버튼의 사이드바도 유지합니다. 그 아래에는 retroTech와 
44bits 배너가 있는데 이건 지워주세요. 어차피 푸터가 있으니까 괜찮습니다. 
본문 한가운데 404 Page not found 라고 이쁘게 써주세요.
이번 건 바로 커밋하지 말고 일단 제가 먼저 볼 수 있게 해주세요.

난 RSS를 많이 쓰는 편이고 블로그가 RSS를 꼭 제공해야 하는 편인데 Claude Code가 알아서 RSS도 만들어줬다. 하지만 다른 블로그를 구독해서 사용하다 보면 블로그가 개편되면서 기존 글이 다 새로운 글처럼 RSS 리더에 등장하는 경우가 종종 있다. 한 번에 읽음 처리하면 되지만 구독자에게 이런 경험을 주고 싶지는 않았다.

그래서 Claude Code로 작업하면서 토큰이 남아도는 Codex로 비교를 시켰다.

1
2
3
https://blog.outsider.ne.kr/atom 와 
http://localhost:8080/rss/index.xml 가 
얼마나 똑같은지 비교해 주세요.

어떤 기준으로 RSS 리더에서 기존의 글이 업데이트되는지도 잘 대답해 주고 딱히 신경 쓰고 있진 않았는데 나는 기존에 Atom 형식으로 RSS 피드를 제공하고 있었는데 Claude Code는 RSS 2.0으로 만들어줬기 때문에 스펙 간의 차이를 물었는데 Atom을 유지해도 문제가 없다고 해서 그냥 Atom을 사용하기로 했다.

2026년 기준으로도 "새 표준이라서 갈아타야 하는" 상황은 아닙니다. 오히려 기존 atom을 유지하는 게 가장 안전하고, 표준성만 보면 Atom 1.0이 RSS 2.0보다 더 정교합니다.

RSS 2.0의 장점은 호환성입니다. 거의 모든 리더가 RSS를 먹습니다. 팟캐스트 생태계도 RSS 기반입니다. 하지만 스펙이 느슨해서 guid가 없거나 날짜가 흔들리면 리더마다 새 글 판단이 달라질 수 있습니다. RSS 스펙도 <guid>를 aggregator가 새 항목 판단에 쓸 수 있다고 설명합니다.

Atom의 장점은 정확성입니다. entry마다 <id><updated>가 필수이고, 특히 Atom 스펙은 마이그레이션/재발행/import/export 때도 atom:id가 바뀌면 안 된다고 명시합니다. 지금 걱정하시는 “기존 글이 다시 새 글처럼 뜨는 문제”에는 Atom 쪽 모델이 더 잘 맞습니다.

라이브 사이트의 Atom 피드를 주고 똑같은 형태로 바꾸도록 수정을 시켰더니 잘 처리해 주었고, 예전부터 개선하고 싶었던 sitemap.xml도 잘 만들어주었다. 이건 따로 지시하지 않고도 처음부터 잘 만들어주긴 했다.

어느 정도 완성되어 가고 있어서 GitHub Actions도 작성하게 했더니 한 번에 잘 작성해 주었다.

접근성과 성능

모든 페이지를 정적 파일로 만들고 CDN을 태울 것이므로 기존보다는 여러모로 성능이 좋아질 것으로 생각하지만, 웹 표준, 접근성, 성능 부분도 챙기고 싶었다.

지적받은 내용을 하나씩 검토하면서 수정할 건 작업 시키고 제외할 건 제외했다. Textcube에서 페이징 된 주소처럼 시간이 지나면 바뀌는 페이지도 검색엔진에 잡히면서 검색 결과와 실제 방문한 페이지가 다른 문제가 있어서 수정했는데 그런 문제도 재발하지 않도록 잘 정리되었고 robots.txt도 새 블로그에 맞게

기존 블로그에서는 개선한 지가 오래되기도 했고 60개 정도의 웹 표준 경고나 오류가 뜨고 있었다.

개선 후에는 오류가 하나도 잡히지 않게 되었다.

웹 접근성 개선과 Core Web Vitals을 개선하게 시켰더니 Pa11yLighthouse로 개선해야 할 목록을 잘 뽑아주었고 하나씩 진행 여부를 확인하면서 작업을 시켰다. 불필요한 의존성이나 사용하지 않는 CSS, JS도 정리하고 preconnect나 이미지 지연 로딩 등의 처리를 했다.

성능을 개선하면 사이드바에 표시하던 creative commons license 아이콘도 SVG로 교체했다.(왼쪽이 기존이고 오른쪽이 개편 후) 맥을 중심으로 모니터가 좋아져서 해상도 낮은 아이콘이나 이미지는 훨씬 더 눈에 거슬린다.

위가 기존 블로그의 PageSpeed Insights 점수이고 아래가 개선한 이후다. 약간 부족한 부분이 있지만 완전히 해소하기 어려운 부분도 있어서 이 정도로 만족한다.

이후에도 계속 검사해야 한다고 했더니 CI에도 알아서 추가해 주었고 이번 목표는 일단 블로그 시스템을 교체하는 거고 이후에도 계속 개선해야 하기에 CI에서 접근성과 성능에 대한 리포트도 간단히 출력하게 해서 비교를 할 수 있게 만들었다.

성능 개선에서 시간을 꽤 썼는데 의존성 정리와 실제 사용하는 아이콘만 추출, 압축 등 꽤 맘에 들게 잘 해주었고, 버전이 낮은 의존성도 업데이트하면서 우려되는 상황 같은 것도 잘 알려주었다. 예를 들어 Font Awesome의 버전 올리면서 아이콘이 달라진 부분과 선택권이 생긴 부분을 잘 잡아서 제안해 주었다. 여러 옵션 중에 제일 좋은 옵션을 추천해 주는 것도 대부분 맘에 들었다.

항상 달고 싶었던 W3C Validation 아이콘을 달까 했지만, 시간이 지나도 여전히 안 이뻐서 그냥 항상 유지하도록 신경아 쓴다는 의미로 HTML5, CSS3, 접근성 아이콘을 푸터에 추가했다.

배포

정적 파일을 AWS CloudFront와 Cloudflare 중 어디로 서빙할지를 고민했다. S3와 CloudFront 조합을 사용하더라도 비용이 많이 나오진 않겠지만 Cloudflare를 쓰면 무료 범위내에서 운영할 수 있을 거 같아서 Cloudflare를 선택했다.

최근 Cloudflare의 CLI가 새로 나오긴 했지만 아직 일부 기능만 제공하는 거 같아서 어려운 일도 아니라 Cloudflare Pages 세 팅은 직접 했다.

하지만 올리려고 하니 Error: Pages only supports files up to 25 MiB in size가 발생했다. Git으로 관리하면서 별로 신경쓰고 있지 않았는데 동영상 같은 파일도 있어서 용량이 약간 큰 파일이 있었다. 찾아보니 Cloudflare Pages에서 오브젝트 스토리지인 R2를 연동하는 문서가 있어서 이를 Claude Code에 던져주고 작업하게 했다.(버킷은 내가 수동으로 만들었다.)

R2 연동 설정은 잘해주었지만, 처음에는 파일 중에 25MB가 작은 파일만 Pages에 올리게 하고 큰 파일만 R2에 올리도록 작업해 주었는데 이렇게 하면 CI에서 도는 것과 Cloudflare에서 빌드하는 게 좀 이상해서 큰 파일의 폴더를 아예 따로 분리해서 관리하고 해당 폴더만 CI에서 R2에 업로드하게 했다. 여기서 CI에 R2 권한을 주어야 하는데 여기서 고생을 많이 했다.(항상 권한이 문제다) Claude Code는 됐다고 하는데 실제로는 권한 오류가 뜨고를 반복하다가 아무리 찾아봐도 R2 특정 버킷에만 접근권한을 줄 수가 없어서 안타깝지만 좀 큰 권한을 줄 수 밖에 없었다.

이 부분까지 정리하고 나니 커밋할 때마다 Cloudflare Pages도 자동 배포하게 구성할 수 있었고 이후부터는 새로운 파일을 추가하고 커밋만 하면 배포되도록 구성했다.

오픈그래프

오픈그래프는 항상 신경 쓰이는 부분이었다. 기존에 Textcube 구조에서 치환자를 잘 조합해서 오픈그래프Twitter Cards를 지원하고 있었지만 깔끔하게 지원하진 못했다.

이미지를 항상 메인 이미지만 쓸 수 있었고 마크다운으로 글을 쓴 뒤에는 콘텐츠 추출이 마크다운으로 그대로 되어서 오픈그래프에도 마크다운 문법이 그대로 출력되는 문제가 있었다.

GitHub처럼 최근 많이 하듯이 썸네일 이미지도 한눈에 잘 들어오게 바꾸고 싶었는데 기존에 Textcube에서 이를 연동하기는 어려워서 문제가 있다는 건 알지만, 그냥 방치하고 있었다.

어차피 빌드 과정이 있으니 이때 각 콘텐츠에 맞게 썸네일 이미지를 찍어낼 수 있게 되었다. 대신 1,700개 정도 되는 이미지를 만들어내야 하므로 빌드때 10여 분이 더 소요되게 되었다. 기존의 빌드된 파일이 있으면 증분 빌드를 하도록 수정하기는 했지만, Cloudflare 빌드에서 캐시를 쓰기가 좀 복잡한 거 같아서 일단 나중에 개선하기로 하고 그대로 두었다.

콘텐츠 검증

새 블로그가 어느 정도 완성되어 가면서 마이그레이션 후에 블로그 글이 기존과 똑같다는 확신이 필요했다. 그래서 Claude Code로 작업을 시키면서 Codex로 콘텐츠 비교 작업을 시켰다. 아까 말했듯이 토큰이 넉넉해서 콘텐츠 비교에도 더 자세하게 할 수 있긴 하지만 그래도 스크립트를 작성하게 시켰다.

그동안 글을 보면서 잘못된 부분을 라이브 블로그와 개발 중인 것을 비교하면서 보게 되어서 이를 쉽게 하도록 만들었다. 끝나고 모든 글을 한 번씩 비교해 보면서 차이점을 보고 싶어서 같은 글을 기준으로 두 블로그의 스크린샷을 찍도록 했다.

이렇게 하면 하나씩 넘겨 보면서 제대로 마이그레이션 되었는지 확인하기 편하겠다 싶었는데 실제로 잘 쓰진 않았다. 스크롤이 세로로 길다 보니까 스크린샷이 제대로 찍히지 않았고 길어서 스크린샷으로 보기도 쉽지 않았다.

이건 글 번호를 입력하면 원본과 로컬을 아이프레임으로 양쪽에 보여주도록 한 프로그램이다. 이 작업을 시킬 때 본문 매치에 대한 조사도 같이 시켰더니 이 프로그램에서도 본문 매치율을 같이 보여주었다. 스크롤 움직이면서 양쪽이 어떻게 다른지 확인할 수 있었다. 사이트 개선하거나 스타일 잘못된 부분 수정할 때도 이 프로그램을 꽤 많이 사용했다.

새 블로그의 개발이 거의 끝나가는 시점에서는 Codex에게 전수조사를 시켰다. 모든 글이 일치해야 하지만 결국 HTML로 렌더링 된 내용을 비교하는 거라서 100% 똑같기는 어렵고 예전 WYSIWYG로 글을 쓰던 시점의 HTML과 이를 마크다운으로 변환한 뒤 다시 HTML로 변환한 글은 완전히 똑같을 수는 없어서 본문 중심으로 비교를 시키더라도 꽤 차이가 있을 수밖에 없었다.

그래서 차이가 크게 나는 것 위주로 위의 아이프레임 비교 도구로 살펴보면서 잘못된 건 수정을 시켰다. 1,700여 개를 다 보진 못하고 낮은 매칭률부터 보다가 어느 정도 문제가 안 발생하기 시작하자 적당한 선에서 만족했다. 일부 깨진 게 있을 수도 있긴 한데 이번에 살펴보다 보니 기존 블로그에서도 이미 깨져서 잘못 나오던 부분도 있어서 전체적으로는 훨씬 깨진 부분이 적어졌다고 생각한다.

후기

엄청나게 많이 써본 것은 아니지만 이렇게 원하는 걸 만들어보니 코딩 에이전트를 쓰는 느낌도 알게 되었다. 업무에서도 쓰긴 하지만 주 업무가 기능 구현이 아니다 보니 많은 시간을 투자하지 않고 있었는데 원하는 요구사항이 머릿속에 확실한 상태에서 코딩 에이전트와 개발해 보니 꽤 신뢰할 수 있게 되었고 그동안 주워들은 정보가 많아서 어느 정도 사용할 수 있었다. 그리고 사람이 병목이라는 말이 어떤 느낌인지도 확실히 체감했다.

이번 작업으로 가장 좋은 건 블로그가 다시 유지보수 가능한 상태가 되었다는 것이다. Textcube를 20년 가까이 사용하면서 하나씩 고장 나면 고치고 하면서 점점 겨우 쓰던 느낌이었고 블로그 메인 기능이 아니면 고장 난 상태로 그냥 쓰고 있었지만 언제 또 주요 기능이 고장 날까 봐 불안해하던 상황이었다. 이번에는 일단 현재 블로그 기능을 그대로 유지한 채 정적 사이트로 마이그레이션하는 게 목적이라서 새로운 기능은 추가하지 않았지만, 그동안 하고 싶은데 못하던 것들도 있어서 앞으로는 쉽게 기능을 추가할 수 있게 되었다.

작업은 4월 12일에 시작해서 5월 6일에 끝났으니 한 달이 채 안 걸렸다. 더 열심히 돌렸으면 더 빨리했겠지만 퇴근하고 와서 작업을 못 한 날도 있어서 쉬는 날에 대부분의 진도를 나갔다. 그래도 사이드 프로젝트를 한참 안 해서 비어 있던 GitHub 잔디도 오랜만에 좀 채웠다.

다른 사람들은 훨씬 더 잘 쓰지만 코딩 에이전트로 이렇게 작업할 수 있다니 신기한 세상이긴 하다. 기존에도 못 만들진 않았겠지만, 이런저런 핑계로 만들지 않고 있었는데 확실히 AI 시대가 되니까 이런 작업도 후딱 할 수 있고 오랫동안 맘속에 있던 부채가 해결된 거 같아서 기분이 좋다. 버그가 계속 발견되겠지만 수정하면 되니까 걱정은 없다.

참고로 새 블로그에서는 Google Adsense도 빼버렸다. 한때 수익이 서버비 이상 정도는 들어오던 때도 있었는데 이유는 모르지만, 지금은 많이 내려가 버렸고, 광고는 더 많아져서 지면을 줄였는데도 블로그 이용도 불편한 수준이다. 예전엔 광고 엄청나게 싫어했다가 또 IT 업계에 있으면서 광고도 좀 알아야 한다는 핑계로 그동안 사용했는데 개편하고 나니 그냥 깔끔하게 유지하고 싶어졌다. 또 이번 달에 딱 Google Adsense가 $100 넘어서 정리하기도 좋은 시점이라고 생각했다.

Models Input Output Cache Create Cache Read Total Tokens Cost (USD)
opus-4-6
opus-4-7
sonnet-4-6
7,221 2,516,203 19,088,613 1,466,523,185 1,488,135,222 $915.36

정액제를 쓰다 보니 Claude Code에 저 비용을 내진 않았지만, 마이그레이션 알바 비용이라고 해도 그리 비싼 비용은 아니라고 생각한다. 체감하는 작업 내용에 비하면 많이 사용하진 않았다.

Model Input Output Reasoning Cache Read Total Tokens Cost (USD)
gpt-5.4 63,379,273 591,182 256,855 58,343,424 63,970,455 $35.38
gpt-5.5 27,451,868 124,608 69,615 25,778,560 27,576,476 $26.32

Codex는 해당 기간에 컨텐츠 비교만 시킨 게 아니라 다른 작업도 좀 하고 있어서 정확하진 않지만, 대략적인 걸 보려고 같이 남겨둔다.(Codex는 ccusage에서 나누어서 볼 수 없어서 그냥 기간으로만 뽑아봤다.)

이젠 블로그 마이그레이션도 끝났으니, CMS를 만들려고 한다. 정확히는 콘텐츠 관리는 아니니 CMS라기 보다는 글 작성 도구라고 할 수 있을 텐데 내가 블로그에 글을 쓰기 편하게 원하는 기능을 넣은 작성 도구를 만들어 보려고 한다.

Valid HTML5 Valid CSS WCAG 2.1 AA tested