이 글은 빈 저장소에서 시작해 nowkim.dev에 배포하고, 조회수·좋아요·댓글까지 붙인 과정의 기록입니다.

필요한 기능 정의#
- 한국어와 영어를 모두 지원할 것
- 글 작성·배포·관리가 편리할 것
- 직접 관리해야 할 서버가 없을 것
- 조회수, 좋아요, 댓글 기능이 가능할 것
- 공짜일 것! (Most Important)
콘텐츠는 Markdown 파일로 저장해 두고, 배포나 댓글처럼 바꿀 수 있는 부분만 외부 도구에 맡기기로 했습니다.
한국어와 영어 지원 - Hugo와 Blowfish#
정적 사이트 생성기로 Hugo를, 테마로 Blowfish를 선택했습니다. Markdown 기반이라 글이 저장소에 남고, 정적 파일만 배포하면 되므로 서버가 필요 없습니다. Blowfish에는 다국어, 다크 모드, 검색처럼 필요한 기본 기능도 이미 갖춰져 있었습니다.
테마는 서브모듈로 추가했습니다.
git submodule add -b main https://github.com/nunocoracao/blowfish.git themes/blowfish저장소를 처음 받을 때는 서브모듈도 함께 받아야 합니다.
git clone --recurse-submodules <repository-url>콘텐츠는 언어별 디렉터리로 나눴습니다.
content/
ko/posts/<slug>/index.md
en/posts/<slug>/index.md각 언어 설정에는 콘텐츠 위치를 지정합니다.
# config/_default/languages.ko.toml
locale = "ko"
label = "한국어"
contentDir = "content/ko"
# config/_default/languages.en.toml
locale = "en"
label = "English"
contentDir = "content/en"같은 글의 한국어·영어 버전에는 같은 translationKey를 넣었습니다. Hugo가 번역본 관계를 인식하고, 언어 전환 링크도 만들 수 있습니다.
translationKey: "hello-world"로컬에서 초안까지 확인할 때는 아래 명령을 사용합니다.
hugo server -D
편리한 작성·배포·관리 - GitHub와 Netlify#
글을 Markdown으로 작성해 GitHub main 브랜치에 푸시하면, Netlify가 Hugo 빌드와 배포를 자동으로 처리합니다.
Markdown 작성 → 로컬 확인 → git push → Netlify 빌드 → 배포Netlify 설정은 대시보드에만 두지 않고 netlify.toml로 저장소에 기록했습니다.
[build]
command = "hugo --gc --minify -b $URL"
publish = "public"
[build.environment]
HUGO_VERSION = "0.164.0"
HUGO_ENV = "production"
TZ = "Asia/Seoul"publish는 Hugo의 결과물 위치입니다. Hugo 버전을 명시해 로컬과 배포 환경의 차이로 빌드가 깨질 가능성을 줄였습니다. 푸시 전에는 아래 명령으로 정적 빌드도 확인할 수 있습니다.
hugo --gc --minify빌드 결과물은 Git에서 관리하지 않았습니다.
public/
resources/
.hugo_build.lockNetlify에서 GitHub 저장소를 연결한 뒤, 위 설정대로 첫 배포가 성공하는지 확인했습니다.

내 도메인 연결하기#
Netlify의 기본 주소 대신 nowkim.dev를 사용했습니다. Cloudflare에서 Netlify가 안내하는 DNS 레코드를 설정하고, Netlify에 커스텀 도메인을 추가한 뒤 HTTPS가 활성화됐는지 확인했습니다.
Hugo 설정의 대표 주소도 함께 변경해야 합니다.
baseURL = "https://nowkim.dev/".dev 도메인은 연 12.5달러였습니다. 생각보다 비쌌다.
도메인 설정과 baseURL이 서로 다르면 사이트맵이나 링크에 이전 주소가 남을 수 있습니다.

서버 없이 조회수·좋아요·댓글 지원 - Netlify Functions, Upstash Redis, Giscus#
정적 사이트에는 조회수와 좋아요를 저장할 곳이 없습니다. 이 기능만을 위해 서버를 운영하지 않기 위해 아래처럼 역할을 나눴습니다.
그리고 이 지점부터 AI 에이전트로 구현하고, 결과를 리뷰하며 커밋했습니다.
| 기능 | 선택한 도구 |
|---|---|
| 정적 사이트 배포 | Netlify |
| 조회수·좋아요 API | Netlify Functions |
| 조회수·좋아요 데이터 | Upstash Redis |
| 댓글 | Giscus + GitHub Discussions |
처음에는 Umami와 GoatCounter도 사용해 봤습니다. 빠르게 시작하기에는 좋았지만, 글 안에 보여 줄 카운터와 좋아요 기능을 원하는 방식으로 묶기에는 제약이 있었습니다. 결국 카운터는 직접 제어하기로 했습니다.
조회수와 좋아요#
Netlify Functions가 Upstash Redis에 값을 저장합니다. Redis URL·토큰·방문자 식별용 비밀 키는 코드에 넣지 않고 Netlify 환경 변수로 설정했습니다.
UPSTASH_REDIS_REST_URL=<Upstash Redis REST URL>
UPSTASH_REDIS_REST_TOKEN=<Upstash Redis REST token>
LIKE_HMAC_SECRET=<long random secret>
VIEW_HMAC_SECRET=<long random secret>
VIEW_SESSION_TTL_SECONDS=28800환경 변수는 .env에 로컬로 보관할 수는 있어도 Git에 커밋하면 안 됩니다.
조회수 API는 브라우저 쿠키의 방문자 ID를 HMAC으로 해시한 뒤, 같은 글을 이미 본 방문자인지 Redis에서 확인합니다. 같은 사람이 새로고침할 때마다 숫자가 올라가는 것을 막기 위해 기본 8시간의 TTL을 둡니다.
글 방문 → 방문자 식별 → 중복 조회 확인 → 없을 때만 조회수 증가좋아요도 같은 방문자의 상태를 Redis에 저장하고, 다시 누르면 취소되는 toggle 방식으로 구현했습니다.
테마에 카운터를 붙일 때는 글 본문뿐 아니라 목록·태그·분류 페이지의 메타 영역도 함께 확인해야 했습니다. 처음에는 한 화면만 수정해 표시가 일관되지 않았고, 결국 layouts/partials/article-meta/에 필요한 partial을 두어 공통으로 렌더링하도록 정리했습니다.

댓글 - Giscus#
댓글을 직접 만들면 인증, 스팸 방지, 데이터 저장까지 관리해야 합니다. 이 부분은 GitHub Discussions를 저장소로 사용하는 Giscus에 맡겼습니다.
설정 순서는 다음과 같습니다.
- 댓글용 GitHub 저장소에서 Discussions를 활성화합니다.
- Giscus 앱을 설치하고 저장소 접근 권한을 부여합니다.
- Giscus 설정 페이지에서 저장소와 Discussion category를 선택합니다.
- 생성된
repoId,categoryId를 설정에 추가합니다. - 글 페이지에 Giscus script를 삽입합니다.
한국어·영어 글이 같은 댓글을 공유하도록 URL 대신 translationKey를 댓글 식별자로 사용했습니다.
<script
src="https://giscus.app/client.js"
data-mapping="specific"
data-term="{{ .TranslationKey }}"
data-lang="{{ .Site.Language.Lang }}"
async>
</script>
무료로 오래 운영하기#
도메인을 사는 것을 제외하면 실제로 돈이 든 지점은 없었습니다. 다만 Netlify의 정책상 배포·푸시·Function 호출 수가 많아지면 과금될 수 있으니 현재 정책을 확인해야 합니다. 개인 블로그라면 당장은 괜찮을 것 같습니다.
중요한 것은 콘텐츠와 기능을 분리하는 것이었습니다. 글은 Markdown으로 남고, 테마·배포·댓글·저장소는 필요하면 각각 교체할 수 있습니다.
마무리#
AI 에이전트 덕분에 쉽게 구현할 수 있어서 좋았고, 어떤 부분에서 의사결정을 해야 하는지만 명확하게 판단할 수 있다면 누구나 쉽게 블로그를 만들 수 있게 되었습니다.
앞으로도 꾸준히 블로그를 작성할 수 있도록 응원해 주세요.
