딸깍 개인 블로그 제작기
Claude Design과 Claude Code의 이어달리기

한 줄도 사람이 안 짠 코드로, 집에 있는 미니 PC 에서 돌아가는 개인 블로그를 만든 이야기. 디자인 시안부터 TLS 갱신까지 사람은 의도와 검수 만 했습니다.
왜 또 만들었나
"개인 블로그 한 번 만들어 봐야지" 라는 생각은 개발자라면 누구나 한 번쯤은 합니다. 저는 그 다음 단계에서 살짝 욕심을 부렸습니다.
AI를 사용해서, 디자인부터 셀프 호스팅까지 한 줄도 직접 안 짜고 끝낼 수 있을까?
코드는 결과물이고, 결과물보다 더 보고 싶은 건 작업 흐름 이었습니다. 그동안 손기술로 채워 본 적 없는 영역(Next.js, 인프라, 디자인) 까지 한 번에 가 보려면, 사람의 손가락 속도가 가장 큰 병목입니다. 그 병목을 빼고 일이 어떻게 흘러가는지 보고 싶었어요.
결과부터 적으면, 이 사이트의 백엔드·프론트엔드·인프라·CI/CD·디자인 시스템까지 제 손으로 친 코드는 없습니다. 모든 라인은 Claude 가 썼고, 저는 의도를 정하고 결과물을 검수했습니다.
디자인 — 대화로 컨셉을 잡습니다
먼저 Claude 웹 쪽과 디자인 대화를 길게 했습니다. 결정해야 할 항목은 사실 몇 개 안 됩니다.
- 톤: "정돈된 개발자 노트북" 같은 느낌. 디자인 자체가 콘텐츠를 가리지 않을 것.
- 모티프: 터미널 프롬프트, ASCII 룰,
$ ls -t ./blog/*.md같은 커맨드 라인 헤더. - 팔레트:
paper-0..4/ink-0..3두 축으로만 — 라이트/다크/세피아 테마가 같은 스케일을 공유하도록. - 타이포: 본문은 Spectral serif, 코드와 강조는 IBM Plex Mono. 한글은 Plex Sans KR 로 fallback 고정 (mono 폰트의 한글이 시스템 명조로 빠지는 걸 막기 위해).
이 결정들을 Claude 한테 컨셉 보드 형태로 정리해 달라고 했고, 그걸 그대로 CSS 변수 토큰에 박았습니다. 디자인 시스템이 멀쩡하게 작동하는지는 컴포넌트 하나씩 띄워 보며 확인했습니다. 색·간격·폰트를 직접 결정하지 않고 어떤 느낌이어야 하는지 만 정해 줘도 일관된 시안이 나오더군요.
백엔드 — Spring Boot 4 + Kotlin + 헥사고날
설계 단계부터 Claude 한테 시켰습니다.
- Spring Boot 4 · Kotlin 2.1 · JDK 25 · JPA · MySQL 8.4
- 헥사고날 아키텍처 —
domain/application/adapter(in/out)로 갈라서, 도메인 패키지에 Spring·JPA 의존이 새 들어가지 않게 (나중에 ArchUnit 으로 강제하기로 했습니다). - Spring Security + JWT — Access/Refresh 분리, 어드민 라우트만 ROLE_ADMIN 게이트.
- Flyway — 마이그레이션은 추가만, 기존 V 파일은 절대 수정 금지. checksum 깨지면 그 자리에서 부팅 실패하니까요.
- Testcontainers — 단위 테스트가 MySQL 컨테이너를 자동 기동. 로컬 실 DB 없이도 테스트가 굴러갑니다.
처음부터 끝까지 "이 도메인을 추가해 줘" 한 줄로 시작하지 않았습니다. 컨텍스트가 모이도록 작은 단위 로 시켰어요. 도메인 모델 → 포트 → 어댑터 → 컨트롤러 → DTO 순으로 끊고, 각 단계에서 변경된 파일만 검토해서 PR 단위처럼 받았습니다. 사람이 들어가는 자리는 경계 결정 입니다. "프로필은 단일 레코드인가, 시계열인가?" 같은 질문은 모델이 추측하지 않게 제가 미리 끊어 줘야 합니다.
엔드포인트는 공개 GET 과 어드민 CRUD 가 명확히 갈라져 있고, 어드민 쪽은 모두 @PreAuthorize("hasRole('ADMIN')") 로 막혀 있습니다. 시드 관리자는 첫 부팅 때 환경변수로만 들어가고, 그 뒤로는 env 에서 빼도 DB 에 남습니다.
프론트엔드 — Next.js 15 + React 19, 서버 컴포넌트로 얇게
프론트 쪽은 더 단순합니다.
- Next.js 15 App Router · React 19 · TypeScript 6 — 서버 컴포넌트로 데이터를 SSR 단계에서 가져오고, 인터랙션이 필요한 자리만
'use client'로 분리. - react-markdown + rehype-highlight + remark-gfm + mermaid — 글 본문은 마크다운, 코드는 highlight.js, 다이어그램은 mermaid.
- 어드민 콘솔 —
/admin하위에 dashboard / posts / pages / media 4 그룹. 글 작성은 자동 저장 (useAutosave) 으로 새로고침에도 살아남도록. - Amplitude — 트래픽이 거의 없는 사이트지만, AI 가 어떤 부분을 잘못 만든 채로 배포됐는지 내가 모르는 상태가 제일 무서워서 클릭/검색/태그 필터/스크롤 깊이 정도는 모두 이벤트로 찍어 둡니다.
여기서도 작은 단위로 시키기 가 핵심이었습니다. "블로그 목록 페이지 만들어 줘" 가 아니라, "태그 카운트 0 인 것은 노출 제외, 활성 태그 재클릭은 해제 토글" 처럼 행동 명세 로 줍니다. 모델이 "그럴듯하지만 빗나간" 코드를 짤 여지를 줄이는 가장 싼 방법이었어요.
인프라 — 미니 PC 한 대로 셀프 호스팅
배포 타깃은 집에 있는 미니 PC 입니다. 이미 immich / n8n / Obsidian sync 같은 자가 호스팅 서비스들을 같은 패턴으로 굴리고 있어서, 같은 구조에 얹기만 하면 됐습니다.
구조는 이렇게 잡혔습니다.
loading diagram…
- Docker Compose — 백/프론트/DB 각자 컨테이너. 업로드 볼륨은 호스트 디스크로 마운트.
- nginx 컨테이너 — 기존 reverse proxy 에 server 블록만 추가.
proxy-network라는 공용 도커 네트워크에 백/프론트가 참여해서, nginx 가 서비스명으로 호출. - Let's Encrypt + certbot — webroot 방식으로 발급, 갱신은 cron.
- GitHub Actions CI/CD — push → 빌드 → 테스트 → Docker Hub 푸시 → SSH 로 미니 PC 접속 →
docker compose pull && up -d --no-deps <service>. 백/프론트 따로 굴러서 한쪽 배포가 다른 쪽을 건드리지 않습니다. - 롤백 — 이미지 태그는 커밋 SHA 로 박혀 있어서, 문제 생기면
TAG=<이전-sha> docker compose up -d한 줄로 되돌립니다.
이 인프라 전체를 제가 명령어를 친 적이 없습니다. Claude 에게 "기존 서비스들이 이런 패턴이니까, 새 서비스를 이 패턴에 끼워 줘" 라고 컨텍스트만 주면, GitHub Actions 워크플로 yaml, nginx server 블록, docker-compose 서비스 정의, 배포 런북 마크다운까지 한 번에 나옵니다. 사람이 한 일은 시크릿(SSH 키, Docker Hub 토큰, JWT 시크릿)을 GitHub Secrets / .env 에 넣어 주는 것 뿐이었습니다.
그래서 사람은 무엇을 했나
흥미로운 부분은 여기입니다. 한 줄도 직접 안 짰다고 해서, 사람이 놀았다 는 뜻은 아닙니다. 오히려 시간의 형태가 바뀌었습니다.
코드를 쓰는 시간이 사라진 자리에 들어온 것들:
- 경계 결정 — 도메인 모델, 권한 분리, 어떤 게 단일 레코드이고 어떤 게 리스트인지.
- 검수 — diff 를 한 줄씩 본다는 뜻이 아니라, "이 변경이 만든 행동" 이 의도대로인지를 직접 띄워서 확인. 특히 모바일 뷰는 빌드만 봐서는 알 수 없어요.
- 보안 경계 — 어드민 라우트, 업로드 경로, SecurityConfig permit 규칙, 시크릿이 절대 코드에 들어가지 않도록 하는 룰.
- 컨텍스트 관리 — Claude Code 의
CLAUDE.md와 memory 시스템에 "이 프로젝트의 결정들" 을 쌓아 둡니다. 다음 세션이 같은 결정을 다시 추측하지 않도록. - 테스트 의도 — 테스트 코드 자체는 AI 가 잘 짭니다. 그런데 어떤 경계 조건이 의미 있는지 는 사람이 짚어 줘야 합니다. 모델이 발견하지 못한 함정이 있을 때 그 자리를 찍어 주는 것 — 그게 사람의 자리입니다.
요약하면, AI 가 코드를 짜기 시작한 뒤로 시키는 사람의 도메인 지식과 판단력이 결과물의 천장 이 됐습니다. 손기술이 빠지지 않은 게 아니라, 손기술이 더 이상 병목이 아니게 됐다는 게 정확한 표현이에요.
무엇을 배웠나
사이드 프로젝트 하나로 모든 걸 일반화하긴 어렵지만, 적어도 이번에 분명히 느낀 것들은 이렇습니다.
- "맡기는 단위" 가 결과 품질을 결정한다. "블로그 만들어 줘" 와 "포스트 목록에서 태그 카운트 0 은 제외" 는 같은 모델한테 시켜도 결과 품질이 다릅니다. 사람이 행동을 먼저 명세할 수 있어야 합니다.
- 컨텍스트는 모델 안이 아니라 프로젝트 안 에 쌓는다. memory, CLAUDE.md, README, DEPLOY 런북 — 다음 세션이 추측하지 않게 만드는 외부 메모리 가 결국 모델 성능을 결정합니다.
- 검수는 diff 가 아니라 행동 으로 한다. UI 변경은 빌드 통과해도 모바일에서 깨질 수 있고, 백엔드 변경은 단위 테스트 통과해도 운영 데이터에서 NPE 가 납니다. 사람이 띄워 보고 만져 봐야 하는 자리는 줄어들지 않습니다.
- 셀프 호스팅이 의외로 가성비가 좋다. 미니 PC 한 대로 immich, n8n, Obsidian sync, 이 블로그까지 굴립니다. AI 가 인프라까지 친구처럼 깔아 주면, 클라우드 청구서 없는 사이드 프로젝트가 더 멀리 갑니다.
다음에 할 일
- ArchUnit 으로 도메인 패키지의 Spring/JPA 의존 금지 룰을 강제.
- 댓글 시스템 (모더레이션 포함) — 이것도 AI 한테 시킬 거리입니다.
- Lighthouse 점수 모니터링을 GitHub Actions 에 붙이기.
P.S.
사실 이 글도 AI 가 썼습니다.
프로젝트 구조와 결정들을 컨텍스트로 주고, "AI 활용 역량이 보이게, 보안 정보는 빼고 써 줘" 한 줄을 시켰을 뿐입니다. 제가 한 일은 — 이번에도 — 의도를 정하고 결과물을 검수 한 것 뿐이고요.