dev notes

바이브 코딩으로 나만의 기술 블로그 만들기

2026-03-3113 min read
공유

들어가며#

티스토리에 주니어 때부터 350개 넘게 글을 썼습니다. 항공학 노트부터 알고리즘 풀이, Spring 프로젝트까지 다양한데 — 포폴로 쓰기엔 좀 애매했습니다.

커스터마이징도 안 되고, 글이 그냥 날짜순으로 쭉 나열되니까 지식 간 연결을 보여줄 수가 없었습니다. 그냥 공부한 흔적이지, 개발 실력을 보여주는 느낌은 아니었습니다.

그러다 Obsidian 기반 블로그를 하나 봤는데, 우측에 Knowledge Graph가 있었습니다. 글 간 연결이 노드와 엣지로 시각화되고, depth 슬라이더로 몇 홉까지 볼지 조절하고, 노드를 잡아 끌 수도 있었습니다. 이걸 보고 바로 만들어야겠다 싶었습니다.

블로그를 직접 만들면 그 자체가 포폴이 되니까, 여기에 그래프까지 얹으면 다른 블로그와 확실히 다릅니다.

Quartz vs Next.js#

처음엔 Quartz를 생각했습니다. Obsidian 마크다운을 그대로 퍼블리시 가능하고, 그래프 뷰도 기본 내장이라 설정만 만지면 바로 쓸 수 있습니다.

근데 포폴용이면 얘기가 다릅니다.

항목QuartzNext.js
첫인상공부 블로그개발 잘하는 사람
커스텀 디자인한계 있음완전 자유
포폴 섹션없음마음대로
그래프 뷰기본 내장D3.js로 직접 구현
작업 시간반나절바이브 코딩으로 하루

Quartz는 빠르긴 한데 결국 남의 템플릿입니다. Next.js로 직접 만들면 블로그 자체가 코드 실력을 보여줍니다.

레퍼런스 서칭#

디자인 방향 잡으려고 세 개 블로그를 뜯어봤습니다.

leerob.io — Vercel VP. 극도로 미니멀. 여백 많고 글에 집중. 네비게이션은 이름이랑 링크 몇 개가 전부.

blog.maximeheckel.com — 3열 그리드. Inter + FiraCode. oklch 색공간. 연도별 글 목록. sticky 헤더에 커맨드 팔레트까지 있었습니다.

joshwcomeau.com — 인터랙티브하고 장난기 있는 스타일. SVG 배경. 카테고리별 필터. Latest + Most Popular 이중 목록.

여기서 뽑아간 것:

  • leerob → 미니멀 레이아웃 (max-width 720px)
  • maximeheckel → 연도별 그룹핑 + sticky 헤더
  • joshwcomeau → 인터랙티브 요소라는 방향

여기에 Obsidian에서 본 Knowledge Graph를 핵심으로 넣기로 했습니다.

기술 스택#

  • Next.js 16 — App Router + SSG. MDX 파일 추가하면 빌드 시 정적 HTML 생성.
  • MDX — 마크다운으로 쓰고 rehype-pretty-code로 코드 하이라이팅, remark-math + rehype-katex로 수식.
  • D3.js — force-directed graph. 노드 간 인력/척력 시뮬레이션으로 자동 배치.
  • Tailwind CSS 4 — CSS 변수 기반 다크/라이트 테마.
  • Giscus — GitHub Discussions 기반 댓글.
  • Vercel — push만 하면 자동 배포. 무료.

CRUD 같은 거 필요 없습니다. MDX 파일만 추가하면 빌드 시 글 목록, 그래프 데이터, SEO 메타가 다 생성됩니다.

Knowledge Graph 구현#

데이터 구조#

빌드 시 lib/posts.tsgetGraphData()가 모든 MDX를 읽어서 노드와 링크를 만듭니다.

노드는 포스트 노드 + 태그 노드. 링크는 3가지:

  1. 태그 연결 — frontmatter의 tags 배열. 같은 태그 공유하면 태그를 허브로 엮임.
  2. 명시적 참조 — frontmatter의 references 배열. 글 간 직접 연결.
  3. 본문 링크 — 마크다운에서 /posts/slug 링크를 정규식으로 자동 감지.

D3.js force simulation#

typescript
const simulation = d3.forceSimulation(nodes)
  .force('link', d3.forceLink(links).id(d => d.id).distance(100))
  .force('charge', d3.forceManyBody().strength(-300))
  .force('collision', d3.forceCollide().radius(40))
  .force('center', d3.forceCenter(width / 2, height / 2));
  • forceLink: 연결된 노드끼리 100px 거리 유지
  • forceManyBody: 노드 간 척력. 값 클수록 넓게 퍼짐
  • forceCollide: 겹침 방지
  • forceCenter: 화면 중앙으로 끌어당김

초기 위치를 랜덤으로 흩뿌려서 매번 다른 레이아웃이 나오고, 0.8초마다 미세한 랜덤 힘을 줘서 약간씩 움직이게 했습니다.

스태거 애니메이션#

한번에 다 나타나면 밋밋해서 태그 → 포스트 → 링크 순서로 시차를 두고 fade-in.

typescript
nodeGroup.transition()
  .delay((d, i) => d.type === 'tag' ? i * 30 : 300 + i * 40)
  .duration(500)
  .style('opacity', 1);
 
link.transition()
  .delay((d, i) => 800 + i * 15)
  .duration(600)
  .attr('stroke-opacity', d => d.type === 'tag' ? 0.4 : 0.7);

여기서 삽질한 게, D3의 .transition()은 selection이 아니라 transition 객체를 반환합니다. link 변수에 transition 결과를 덮어쓰면 tick 핸들러에서 위치 업데이트가 안 됩니다. transition은 별도로 호출해야 합니다.

350개 글 분석과 마이그레이션#

전수 조사#

블로그 카테고리를 전부 순회하면서 글 목록을 수집했습니다. 알고리즘 풀이, 항공학, RSM, 일상 같은 건 빼고, 나머지는 하나씩 읽으면서 기술적 깊이, 독창성, 완성도로 평가했습니다.

150개 넘게 본문까지 읽은 결과:

  • Tier 1 (필수 6건): Thread Starvation 해결기, Vanilla Java HTTP Server, 람다 디슈거링, HashMap 성능 테스트, 쿠폰 시스템, 구매 이력 관리
  • Tier 2 (추천 12건): DispatcherServlet, 리팩토링, @Transient, 파싱, WireShark, String Pool, JVM 등
  • ML 6건: RSM 시리즈 전체
  • 제외 ~320건: 주니어 따라하기, 공식문서 번역, 알고리즘 풀이, 항공학 등

본문 추출 삽질#

티스토리는 JS로 본문을 렌더링합니다. 서버 HTML에는 메타데이터만 있고 실제 내용은 클라이언트에서 동적으로 로드됩니다. 그래서 HTTP 요청으로는 본문을 못 가져옵니다.

시도 1: WebFetch — 실패. 제목이랑 태그만 나오고 본문은 빈 상태.

시도 2: RSS — 부분 성공. RSS에 최근 4개 글은 전체 본문이 있었다. 근데 오래된 글은 안 나옴.

시도 3: Puppeteer MCP — 성공. 헤드리스 브라우저로 페이지를 렌더링하고 DOM에서 .contents_style로 본문 추출. 마크다운 변환 스크립트 짜서 해결.

javascript
const el = document.querySelector('.contents_style');
function toMd(node) {
  // h2, h3 → 소제목
  // pre > code → 코드블록
  // img[data-src] → 이미지 (lazy-load 대응)
  // ...
}
return toMd(el);

이미지는 src가 아니라 data-src에 있어서 (lazy loading) 따로 처리해야 했습니다.

MDX 빌드 에러#

추출한 마크다운을 MDX에 넣으면 빌드가 깨졌습니다.

JSX 주석{/* TODO */}가 JSX로 파싱됨. HTML 주석 <!-- -->도 안 됨. 그냥 전부 삭제.

제네릭 타입Function<String, Integer> 같은 게 코드 블록 밖에 있으면 JSX 태그로 인식. 백틱으로 감싸서 해결.

LaTeX$$\frac{}{}$$ 안의 중괄호가 JSX로 파싱. remark-math + rehype-katex 설치해서 수식을 먼저 처리하게 순서 조정.

태그 체계 — 고립 클러스터 문제#

태그를 기술명으로만 넣었더니 그래프가 5개 섬으로 나뉘었습니다. ML은 완전히 동떨어져 있었고요.

브릿지 태그라는 걸 만들어서 해결했습니다. 카테고리를 넘나드는 공통 개념을 태그로 만드는 겁니다.

브릿지 태그뭘 연결하나
성능 최적화HashMap ↔ DB 파싱 ↔ 버퍼풀 ↔ Thread Starvation
메모리 관리JVM ↔ String Pool ↔ 버퍼풀 ↔ POSIX 공유 메모리
동시성 제어MySQL 락 ↔ Thread Starvation ↔ Mach IPC
데이터 검증HashMap 벤치마크 ↔ ML 모델 평가

Backend, DevOps 같은 뭉뚱그려진 태그는 다 빼고, 실제 기술적 관점이 드러나는 태그로 교체.

5개 섬이 1개로 합쳐졌습니다.

디자인#

시안 테마#

처음엔 보라색(indigo)이었는데 시안(#22d3ee)으로 바꿨다. 차갑고 깔끔한 느낌이 기술 블로그에 더 맞았다. 그래프 노드, 링크, 버튼, 선택 색상까지 전부 시안으로 통일.

Pretendard#

Geist는 영문 전용이라 한글이 어색했다. Pretendard Variable을 CDN으로 넣고 letter-spacing, word-break, font-smoothing 조정.

그래프 히어로#

메인에 그래프를 화면 70%로 깔고, 그래디언트 오버레이 위에 소개 텍스트를 올렸습니다. 노드 드래그, 줌 가능해서 방문자가 자연스럽게 만지게 됩니다.

카테고리 아코디언#

26개만 돼도 목록이 길어집니다. 카테고리별로 접었다 펼 수 있게 하고, 안에서 연도별로 묶었습니다.

관련 글 추천#

글 페이지 우측에 사이드바. 참조(+40점), 본문 링크(+30점), 공유 태그(+10점/개)로 연관도 계산해서 프로그레스 바와 같이 보여줍니다.

배포#

GitHub private 레포에 push하면 Vercel이 자동 빌드+배포. 소스는 숨기고 블로그만 공개. 댓글은 Giscus로 GitHub Discussions 연동.

하루 만에 골격을 잡긴 했지만, 결국 중요했던 건 속도 자체보다 뭘 직접 결정하고 뭘 넘길지를 내가 알고 있었다는 점이었습니다. 기술 선택, 태그 구조, 그래프를 어떤 경험으로 보이게 할지는 내가 정했고, 반복 작업이나 마이그레이션처럼 시간이 많이 드는 부분은 AI에 맡겼습니다.

지금 구조에서는 MDX만 추가하면 글이 발행되고, tags와 references를 넣으면 그래프가 자연스럽게 확장되고, push하면 바로 배포됩니다. 그래서 이 블로그는 한 번 만들고 끝나는 결과물이라기보다, 글이 쌓일수록 점점 더 포트폴리오다워지는 쪽에 가깝습니다.

Connected Notes