byminseok.com

하루 만에 22개 기능을 쌓고, 다음 날 전부 리팩토링한 이야기

8년 전에 Jekyll + GitHub Pages로 블로그를 만들었다. 컴공과 학부생 시절, “프로그래밍으로 내 블로그를 만들고 싶다”는 마음으로 어찌저찌 삽질해서 theorakim.github.io를 띄웠다. 그리고 방치했다.

그 사이에 네이버 블로그, 티스토리를 거쳤고, 작년에 Bear Blog으로 옮기면서 처음으로 개인 도메인(byminseok.com)을 Porkbun에서 사서 붙였다. 2013년부터 여기저기 흩어져 있던 글들을 Bear Blog에 모아두고, 한동안 잘 썼다.

전환점은 Claude Code였다. 바이브코딩을 하면서 “내가 직접 코드를 만질 수 있는 환경”의 가능성을 체감하기 시작했다. Bear Blog은 편하지만 커스터마이징에 한계가 있었다. 호스팅 비용도 내고 있었고. 그러던 차에 Claude Max 구독을 이미 해둔 상태라, 주말에 갑자기 “지금 옮기자”는 생각이 들었다. 그렇게 마이그레이션이 시작됐다.

하루 동안 거의 10시간을 붙어서 22가지 작업을 했다. Bear Blog에서 Jekyll로 100개 글을 옮기고, 태그 필터, 스프레드 레이아웃, 타이핑 인트로, 포트폴리오 페이지, 커스텀 도메인 연결까지. 기능은 전부 잘 동작했다. 문제는 코드였다.

하나씩 기능을 붙이다 보니 같은 로직이 여러 파일에 복사되고, 한 파일이 여러 역할을 떠안고, 색상값이 변수 없이 직접 박혀 있었다. 동작은 멀쩡한데 코드를 들여다보면 “이거 나중에 뭐 하나 바꾸려면 세 군데를 고쳐야 하겠구나”가 보이는 상태. 기능 추가의 속도를 내려면 이 상태로 계속 가면 안 됐다.

다음 날, 새 포스트를 올리려고 코드를 열었는데 내가 만든 블로그의 구조가 이해되지 않았다. 어제 10시간 동안 만든 건 나인데, 정작 이 파일이 무슨 역할인지, 저 코드가 왜 거기 있는지 모르겠는 상태. 리팩토링을 해야겠다고 생각한 건 코드 품질 때문이기도 했지만, 솔직히 내가 내 블로그를 이해하고 싶어서이기도 했다.

리팩토링 전 상태

구체적으로 뭐가 문제였냐면:

인라인 JS 291줄. archive.html에 132줄, home.html에 143줄, post.html에 16줄. 전부 <script> 태그 안에 인라인으로 들어있었다. 특히 스프레드 리더 로직이 archive.html(103줄)과 home.html(70줄)에 거의 같은 코드로 중복되어 있었다. fetch로 글을 가져오고, DOMParser로 파싱하고, 캐시하고, 렌더링하는 패턴이 두 벌.

태그 라우팅 2곳 중복. 글의 태그를 보고 어느 아카이브 페이지⟨writings/techphil/devlog⟩에 속하는지 결정하는 Liquid 조건문이 post.htmlhome.html에 똑같이 들어있었다. 나중에 새 카테고리를 추가하려면 두 파일을 동시에 수정해야 한다.

{% if tag == "기술철학" or tag == "STS" or tag == "Art-n-Tech" or tag == "human-ai" %}
  {% assign archive_url = "/techphil/" %}
{% elsif tag == "개발기록" %}
  {% assign archive_url = "/devlog/" %}
{% endif %}

이게 두 군데에 있었다.

하드코딩 색상. 테라코타 포인트 색상 #C15F3C과 배경 #F4F3EE_layout.scss에 직접 박혀 있었다. 홈 스프레드 활성 링크, 뉴스레터 버튼, 호버 상태까지 합치면 6군데. 색을 바꾸고 싶으면 grep으로 찾아서 하나하나 바꿔야 했다.

SCSS 뒤섞임. 스프레드 레이아웃 CSS가 _layout.scss(body 폭 규칙, 활성 링크)와 _post.scss(패널, 토글, 확대모드, 플레이스홀더)에 나뉘어 있었다. 스프레드 관련 스타일을 수정하려면 두 파일을 왔다 갔다 해야 했다.

죽은 파일. _nav.scss는 주석 2줄, _includes/nav.html은 어디서도 include되지 않는 파일. 어제 네비게이션을 default.html 헤더에 직접 넣으면서 쓸모가 없어진 건데 파일은 남아있었다.

React나 Next.js로 갈아탈까

Jekyll은 2008년에 GitHub 창업자 Tom Preston-Werner가 만든 정적 사이트 생성기다. Ruby 기반이고, Markdown으로 글을 쓰면 HTML로 변환해서 사이트를 만들어준다. 템플릿 엔진은 Liquid, 스타일은 SCSS. 18년 된 도구인데 GitHub Pages가 네이티브로 지원하기 때문에 개인 블로그 쪽에서는 아직도 많이 쓰인다. 요즘은 Next.js, Astro, Hugo 같은 후발주자들이 훨씬 인기가 많지만.

리팩토링을 시작하기 전에 한 가지 진지하게 고민한 게 있다. “이 참에 Jekyll을 버리고 React + Next.js로 옮기면 어떨까?”

이유는 있었다. 컴포넌트 기반으로 코드를 나누면 지금의 중복 문제가 구조적으로 해결된다. 스프레드 리더를 <SpreadReader /> 컴포넌트 하나로 만들면 archive와 home에서 props만 다르게 넘기면 되니까. 태그 라우팅도 데이터 파일 하나에서 관리하는 게 당연한 구조가 되고. 그리고 솔직히 개발자 경험도 더 좋다. hot reload, 타입 체크, import 구조.

그런데 결론은 Jekyll 유지였다. 이유:

지금 블로그에 동적 기능이 거의 없다. 스프레드 리더가 가장 복잡한 JS인데, 그것도 fetch + DOM 조작이 전부다. 상태 관리 라이브러리가 필요한 수준이 아니다. React를 쓰면 번들 사이즈, 빌드 파이프라인, 의존성 관리라는 새로운 복잡성이 생기는데 그만큼의 복잡한 UI가 없다.

GitHub Pages 무료 호스팅. Jekyll은 GitHub이 네이티브로 지원한다. push하면 빌드+배포가 알아서 된다. Next.js로 가면 Vercel이나 별도 빌드 파이프라인이 필요하다. 돈이 드는 건 아니지만 관리 포인트가 하나 더 생긴다.

마이그레이션 비용 대비 효과. 100개 포스트의 Liquid 템플릿을 JSX로 전환하고, SCSS를 CSS-in-JS나 Tailwind로 바꾸고, 빌드 설정을 새로 잡는 데 드는 시간이, 지금 Jekyll 코드를 정리하는 것보다 훨씬 크다. 그리고 결과물의 기능은 완전히 동일하다.

Liquid + vanilla JS로도 충분히 깔끔한 구조가 가능하다. 어제의 문제는 Jekyll의 한계가 아니라 내가 기능을 급하게 쌓으면서 정리를 안 한 것이 원인이었다. _data 디렉토리, 외부 JS 파일, SCSS 분리 같은 Jekyll의 기존 기능만으로도 구조화가 된다.

결국 “도구를 바꾸는 것”이 아니라 “도구를 제대로 쓰는 것”이 답이었다.

리팩토링 후 구조

작업 내용을 설명하기 전에, 리팩토링 후의 파일 구조를 먼저 보자. 각 관심사가 하나의 파일에만 존재하는 것이 핵심이다.

리팩토링 후 파일 아키텍처

테라코타 색 테두리가 “single source of truth” — 해당 관심사에 대해 이 파일 하나만 수정하면 되는 곳이다. categories.yml을 바꾸면 태그 라우팅이, _variables.scss를 바꾸면 색상이, spread.js를 바꾸면 스프레드 동작이 전체 사이트에 반영된다.

스프레드 리더의 동작 흐름도 정리해두면:

스프레드 리더 동작 흐름

데스크탑(960px 이상)에서 글 목록의 링크를 클릭하면 JS가 가로채서 fetch → 파싱 → 오른쪽 패널 렌더링을 수행한다. 모바일에서는 JS가 개입하지 않고 기본 링크 동작으로 페이지 이동. 새로고침 시에는 sessionStorage에 저장해둔 아카이브 URL로 리다이렉트해서 스프레드 상태를 복원한다.

리팩토링 작업

Claude Code로 계획을 먼저 세우고, 검증한 뒤, 단계별로 실행했다. 각 단계마다 빌드를 돌려서 깨진 게 없는지 확인하면서 진행.

1. 태그 라우팅 중앙화

_data/categories.yml을 만들었다.

- id: techphil
  url: /techphil/
  tags:
    - 기술철학
    - STS
    - Art-n-Tech
    - human-ai

- id: devlog
  url: /devlog/
  tags:
    - 개발기록

post.htmlhome.html의 하드코딩된 조건문을 site.data.categories 순회로 교체했다. 이제 새 카테고리를 추가하려면 YAML 파일에 한 줄만 추가하면 된다. 두 파일을 동시에 수정할 필요가 없어졌다.

2. CSS 변수 정비

_variables.scss에 테라코타 색상 변수를 추가했다.

--color-accent: #C15F3C;
--color-accent-hover: #A84E30;
--color-accent-text: #F4F3EE;

다크모드 대응도 함께 넣었다. 스프레드 레이아웃 폭도 변수화해서 --spread-list-width--spread-body-width로 한 곳에서 관리하게 만들었다. _layout.scss에 흩어져 있던 calc() 체인이 변수 참조로 바뀌었다.

3. SCSS 재구성

_sass/_spread.scss를 새로 만들고, _layout.scss_post.scss에 흩어져 있던 스프레드 관련 스타일을 전부 이동했다. 이동한 항목:

  • body:has(.spread), body:has(.post__back) 폭 규칙 ← _layout.scss
  • .home-spread-link.is-reading 활성 스타일 ← _layout.scss
  • .post__back 목록 버튼 ← _post.scss
  • .spread 전체 블록 (패널, 토글, 확대모드, 플레이스홀더) ← _post.scss

이제 스프레드 레이아웃에 대해 뭔가 수정하고 싶으면 _spread.scss 한 파일만 보면 된다. _layout.scss는 본래 역할인 body·header·footer·section 스타일만, _post.scss는 포스트·아카이브 목록·태그 필터 스타일만 담당한다.

4. JS 외부 파일화

assets/js/ 디렉토리를 만들고 3개 파일로 분리했다.

파일 역할 원본
spread.js 스프레드 리더 코어 (fetch, 캐시, 렌더, 토글, ?read= 처리) archive.html + home.html
tag-filter.js 태그 필터 UI archive.html
typing-intro.js 홈 타이핑 인트로 home.html

핵심은 spread.js의 설계다. archive와 home에서 중복되던 스프레드 로직을 Spread.init(options) 형태로 통합했다. 각 페이지는 설정값만 전달한다.

<!-- archive.html -->
<script src="/assets/js/spread.js"></script>
<script>
Spread.init({
  reader: 'spread-reader',
  links: '.post-list__link',
  extract: 'post'
});
</script>
<!-- home.html -->
<script src="/assets/js/spread.js"></script>
<script>
Spread.init({
  reader: 'home-reader',
  links: '.home-spread-link',
  extract: 'content',
  handleQuery: false,
  extraLinks: function(isActive) { /* 최근 글 → 아카이브 이동 */ }
});
</script>

extract: 'post'는 fetch한 HTML에서 .post article을 추출하고(archive용), extract: 'content'.post__content.portfolio를 추출한다(home용). handleQuery: false는 홈에서는 ?read= 쿼리 처리를 하지 않도록 한다.

post.html의 복원 스크립트(8줄)와 목록 버튼 스크립트(8줄)는 인라인으로 유지했다. 페이지 로드 즉시 실행되어야 하는 리다이렉트 로직이라 외부 파일로 빼면 FOUC⟨Flash of Unstyled Content⟩ 같은 깜빡임이 생길 수 있어서.

5. 죽은 파일 정리

  • _sass/_nav.scss 삭제 (주석 2줄)
  • _includes/nav.html 삭제 (어디서도 include 안 됨)
  • assets/css/main.scss에서 @import "nav" 제거, @import "spread" 추가

결과

빌드 에러 없이 통과. 브라우저에서 전체 기능을 확인했다.

변경된 파일 수:

  • 수정: 7개 (_variables.scss, _layout.scss, _post.scss, main.scss, archive.html, home.html, post.html)
  • 생성: 5개 (categories.yml, _spread.scss, spread.js, tag-filter.js, typing-intro.js)
  • 삭제: 2개 (_nav.scss, nav.html)

인라인 JS: 291줄 → 16줄 (init 호출만 남음)

구조적으로 달라진 점:

  • 태그 라우팅 규칙이 한 곳(_data/categories.yml)에만 존재
  • 스프레드 로직이 한 곳(spread.js)에만 존재
  • 색상값이 한 곳(_variables.scss)에만 존재
  • 스프레드 CSS가 한 곳(_spread.scss)에만 존재

기능은 하나도 바뀌지 않았다. 사용자 입장에서 보면 리팩토링 전과 후가 완전히 동일하다. 달라진 건 다음에 기능을 추가할 때 수정할 파일이 하나로 줄었다는 것.

배운 것

어제 22가지 기능을 하루 만에 구현한 건 Claude Code 덕이 크다. 그런데 빠르게 만들 수 있다는 건 빠르게 기술 부채가 쌓인다는 뜻이기도 하다. AI와 함께 코딩할 때 “일단 동작하게 만들기”의 속도가 빨라지는 만큼, “제대로 구조화하기”를 의식적으로 따로 챙겨야 한다.

다행인 건, 리팩토링도 빠르다는 점이다. 계획 세우고 검증하고 실행하는 전체 과정이 한 세션에 끝났다. 기능 구현 → 구조 정리를 하루 간격으로 반복하는 리듬이 생각보다 괜찮았다.

그리고 Jekyll이 “오래된 도구”라고 구조적 한계가 있는 건 아니었다. _data, 외부 JS, SCSS 파일 분리 같은 기본기만 잘 써도 충분히 깔끔한 코드가 나온다. 도구를 탓하기 전에 도구를 제대로 쓰고 있는지 먼저 볼 일이다.

만드는 방식이 달라졌다

리팩토링을 끝내고 나서 든 생각이 하나 있다. 8년 전에 이 블로그를 처음 만들 때와 지금, 프로그래밍으로 무언가를 만드는 방식이 완전히 달라졌다.

8년 전에는 이랬다. Jekyll 공식 문서를 읽고, Ruby가 뭔지 공부하고, Liquid 문법을 익히고, SCSS를 배우고, 하나하나 삽질하면서 겨우 블로그를 띄웠다. 배포 버튼 누르기가 무서웠다. 뭔가 잘못될까 봐. 순서가 명확했다: 공부 → 이해 → 구현 → 배포.

이번에는 달랐다. “Bear Blog 글을 Jekyll로 옮기고 싶어”라고 말하는 것에서 시작했다. 10시간 뒤에 22개 기능이 동작하는 블로그가 있었다. 그리고 다음 날, 내가 만든 블로그의 구조가 이해되지 않아서 리팩토링을 시작했다. SCSS가 뭔지, YAML이 뭔지, Jekyll이 정확히 어떤 역할인지를 — 만든 다음에 공부했다.

순서가 뒤집혔다: 구현 → 배포 → 이해 → 정리.

이게 단순히 “AI가 대신 코딩해줘서 편해졌다”는 이야기는 아니다. 학습의 방향이 바뀐 것이다. 예전에는 이해가 구현의 선행조건이었다. 모르면 못 만들었다. 지금은 완성된 결과물을 보면서 “이게 왜 이렇게 돌아가지?”를 역추적하는 방식으로 배운다. 오히려 동작하는 코드를 앞에 두고 질문하는 게 빈 에디터 앞에서 문서를 읽는 것보다 빠를 때가 있다.

물론 이 방식에는 리스크가 있다. 어제의 내가 그랬듯, 동작은 하는데 구조를 모르는 상태가 된다. AI가 만들어준 코드 위에 AI가 만들어준 코드를 쌓다 보면, 내가 진짜로 이해하고 있는 것과 그렇지 않은 것의 경계가 흐려진다.

그래서 리팩토링이 중요한 것 같다. 단순히 코드 품질을 위해서가 아니라, 내가 만든 것을 내가 이해하기 위해서. 기능 구현은 AI와 함께 빠르게, 구조 정리는 내가 직접 뜯어보면서 천천히. 이 두 박자가 맞아야 “바이브코딩”이 “그냥 남이 만든 걸 쓰는 것”이 아니라 “내가 만드는 것”이 된다.