블로그 방문자 수, 직접 카운팅 시스템 구축기
📊 블로그 방문자 수, 직접 카운팅 시스템 구축기
기존에 https://hits.seeyoufarm.com 서비스를 활용해 블로그 방문자 수를 추적하고 있었습니다. 하지만 어느 시점부터 이 서비스가 더 이상 작동하지 않게 되었고, 공식적으로도 유지보수가 중단된 상태였습니다. 사용자 수가 많아지면서 카운트가 정확하게 반영되지 않는 현상도 자주 발생했죠.
그래서 직접 방문자 수 카운팅 시스템을 구축하기로 결심했습니다.
내가 원하는 형식으로 SVG 뱃지를 만들고, 다양한 플랫폼에서도 사용할 수 있도록 API 형태로 제공하는 것이 이번 작업의 핵심 목표였습니다.
🎯 구축 목표 및 기능 요약
- HTML
<img>태그로 삽입 가능한 SVG 뱃지 생성 - 방문 시마다 조회수를 Firestore에 저장
- 다양한 블로그 플랫폼(Velog, Tistory, 개인 블로그)에서도 사용 가능하도록 공개 API 제공
- 쿼리 스트링의
postId값을 기준으로 조회수를 구분하여, 각 포스트/페이지별로 개별 카운팅 가능 - API 응답은 실시간 SVG 이미지로 구성되며 GitHub README나 웹 페이지 내 어디에서든 렌더링 가능
- 최종적으로는
https://hits.beenslab.com도메인으로 API를 제공
⚙️ 전체 아키텍처 및 기술 스택
1. AWS Lambda
조회수를 카운트하고, SVG 이미지를 반환하는 핵심 처리 로직을 담당합니다.
TypeScript로 작성되어 있으며, Firebase Admin SDK를 통해 Firestore와 통신합니다.
Lambda 함수 주요 흐름
// src/index.ts (발췌)
export const handler = async (event: APIGatewayProxyEvent): Promise<APIGatewayProxyResult> => {
const postId = event.queryStringParameters?.postId;
if (!postId) {
return {
statusCode: 400,
body: 'Missing postId',
};
}
// Firestore에서 조회수 업데이트
const { totalHits, todayHits } = await updateHitCount(postId);
// SVG 생성
const svg = generateSvgBadge(totalHits, todayHits);
return {
statusCode: 200,
headers: { 'Content-Type': 'image/svg+xml' },
body: svg,
};
};
postId파라미터를 받아 Firestore에서 해당 포스트의 조회수를 읽고, 증가시킵니다.- SVG 뱃지를 동적으로 생성하여 반환합니다.
2. Firebase Firestore
Firestore는 조회수 저장용 NoSQL DB로 선택했습니다.
다음과 같은 구조로 문서를 구성했습니다:
beenslab (문서)
└── posts (컬렉션)
└── {postId} (문서)
├── total_hits: number
├── today_hits: number
└── last_hits_date: string (YYYY-MM-DD)
Firestore 트랜잭션 예시
// src/firestore.ts (발췌)
export async function updateHitCount(postId: string) {
const postRef = db.collection('posts').doc(postId);
await db.runTransaction(async (transaction) => {
const doc = await transaction.get(postRef);
// ...조회수 증가 및 todayHits, last_hits_date 갱신 로직...
transaction.set(postRef, { total_hits, today_hits, last_hits_date }, { merge: true });
});
// ...생략...
}
3. AWS API Gateway
API Gateway는 퍼블릭한 HTTP 엔드포인트 역할을 하며, Lambda 함수에 요청을 프록시합니다.
GET /hits?postId=... 형태로 호출되며, 응답은 Content-Type: image/svg+xml 헤더를 포함한 SVG 문자열입니다.
4. 커스텀 도메인 연결
- **AWS Certificate Manager(ACM)**에서 SSL 인증서 발급
- 외부 도메인 서비스에서 CNAME 레코드로 API Gateway에 연결
- API Gateway에서 커스텀 도메인 매핑
🌐 다양한 플랫폼에서 사용
HTML 예시
<img src="https://hits.beenslab.com/hits?postId=my-post-uuid" alt="hit-count" />
Velog/Markdown 예시

postId는 블로그 포스트의 고유 ID 또는 페이지별 구분 키로 자유롭게 설정할 수 있습니다.
🧊 Lambda 콜드 스타트 문제와 CloudWatch 해결책
AWS Lambda의 특성상, 일정 시간 동안 호출이 없으면 함수가 언로드되고, 다음 호출 시 콜드 스타트가 발생합니다.
SVG 이미지 요청처럼 빠른 응답이 필요한 경우 체감될 수 있습니다.
해결 방법
- CloudWatch EventBridge 규칙을 생성하여 5분마다 Lambda를 호출하도록 설정
- Lambda를 **웜 상태(warm start)**로 유지하여, 실제 API 응답 속도를 100~300ms 이내로 유지
💻 소스코드
모든 코드는 GitHub에 오픈소스로 공개되어 있습니다:
🔗 beenchangseo/beenslab-hitmark
주요 파일 구조
src/
├── index.ts # Lambda 엔트리포인트
├── firestore.ts # Firestore 트랜잭션 로직
└── svg.ts # SVG 뱃지 생성 함수
SVG 생성 함수 예시
// src/svg.ts (발췌)
export function generateSvgBadge(totalHits: number, todayHits: number): string {
return `
<svg width="120" height="20" ...>
<rect ... />
<text x="10" y="15">Total: ${totalHits}</text>
<text x="10" y="35">Today: ${todayHits}</text>
</svg>
`;
}
📈 향후 개선 방향
- 중복 카운팅 방지
- 동일 IP 또는 User-Agent 기반 중복 필터링
- 일정 시간 이내 중복 요청 제한 등 로직 추가 예정
- AWS 비용 최적화
- CloudFront 캐싱, SVG 정적 생성, Lambda 호출량 최적화 등
- 통계 페이지 제공
- 관리자/운영자용 대시보드 연동 예정
마치며
직접 방문자 수 카운팅 시스템을 구축하면서
- 서버리스 환경에서의 실시간 데이터 처리
- SVG 동적 생성
- AWS 인프라와 외부 도메인 연동
등 다양한 경험을 할 수 있었습니다.
누구나 쉽게 자신의 블로그/플랫폼에 방문자 카운터를 붙이고 싶다면
beenslab-hitmark 깃허브에서 소스코드를 참고해보세요!