서버 없이 조회수를 보여줄 수 있을까

정적 사이트에서 운영 서버 없이 조회수와 방문자 수를 보여주고 싶을 때 GA4, Cloudflare, Plausible, Umami, 정적 JSON 방식을 비교했습니다.

정적 사이트에도 조회수나 방문자 수를 붙일 수 있습니다. 다만 버튼을 누르듯 숫자를 1씩 올리는 카운터로 만들면 구조가 바로 무거워집니다. 쓰기 가능한 저장소, 중복 방문 처리, 봇 차단, 조작 방지까지 같이 따라오기 때문입니다.

그래서 작은 블로그에서는 방향을 바꾸는 편이 낫습니다. 조회수는 실시간 기능으로 만들지 말고, 분석 도구가 이미 모은 집계값을 정적 JSON으로 배포합니다. 사이트는 그 파일을 읽기만 합니다.

이 방식이면 운영 서버 없이도 “최근 7일 인기 글”, “글별 누적 조회수”, “대략적인 방문 흐름”을 보여줄 수 있습니다. 대신 실시간성과 정확한 개인별 카운팅은 포기합니다. 정적 사이트에서는 이 포기가 기능을 단순하게 만듭니다.

숫자를 공개하려는 수요 자체는 작지 않습니다. Plausible은 홈페이지에서 19,000명 이상의 유료 고객과 2,600억 회 이상의 추적 pageview를 공개합니다. Google Analytics Data API도 “지난 28일 동안 내 사이트의 상위 10개 페이지의 페이지 조회수”를 공식 예시로 듭니다. 문제는 숫자를 보여줄 수 있느냐가 아니라, 작은 사이트가 어떤 수준의 숫자를 책임질 것인가입니다.

먼저 정해야 할 것

조회수 기능은 숫자를 어디에 보여줄지보다 숫자의 의미를 먼저 정해야 합니다.

원하는 것 서버 없이 가능한가 판단
전일 기준 글 조회수 가능 분석 도구에서 집계해 JSON 생성
최근 7일 인기 글 가능 매일 갱신하면 충분
실시간 현재 방문자 어렵다 외부 대시보드에서 보는 편이 낫다
클릭할 때마다 증가 어렵다 쓰기 저장소와 남용 방지가 필요
좋아요 위험하다 조작, 중복, 삭제 요청 처리가 필요

작은 블로그라면 “정확한 카운터”보다 “방향을 보여주는 숫자”가 더 유용합니다. 예를 들어 글 카드에 최근 7일 조회 128 정도를 표시하면 충분합니다. 이 숫자는 실시간이 아니어도 독자에게는 맥락이 되고, 운영자에게는 어떤 주제가 반응을 얻는지 알려줍니다.

선택지는 생각보다 많다

처음에는 GA4나 Cloudflare만 떠올리기 쉽지만, 조회수 표시를 위해 쓸 수 있는 도구는 더 많습니다. 여기서 봐야 할 기준은 기능 수가 아닙니다. 글 URL 단위로 집계값을 안정적으로 가져올 수 있는지, 그 값을 공개 화면에 보여줘도 설명이 가능한지입니다.

방법 잘 맞는 경우 표시 방식 주의할 점
GA4 Data API 이미 GA4를 쓰고 있고 SEO 지표도 볼 때 Actions가 API를 읽어 JSON 생성 설정과 권한 관리가 번거롭다
Cloudflare Web Analytics + GraphQL Cloudflare를 앞단에 두고 있을 때 GraphQL로 집계값을 가져와 JSON화 Cloudflare 통과 트래픽 기준이다
Plausible Stats API 유료 분석 도구를 써도 괜찮을 때 pageviews, visitors 조회 API 조건과 요금제를 봐야 한다
Umami 분석 도구 자체를 소유하고 싶을 때 stats, pageviews, active 조회 셀프호스팅이면 운영 책임이 생긴다
GoatCounter 가볍고 단순한 공개 통계를 원할 때 stats API 또는 export 사용 단순 집계에 더 잘 맞다
Simple Analytics 개인정보 친화적인 유료 도구를 원할 때 Stats API 또는 Export API 사용 공개 설정과 인증 조건을 봐야 한다
Vercel Web Analytics 배포가 이미 Vercel일 때 대시보드 중심으로 확인 커스텀 표시는 제약이 있다
Tinylytics, Counter.dev 작은 공개 통계가 먼저 필요할 때 제공 위젯이나 공개 통계 활용 장기 운영과 데이터 이전성을 봐야 한다

GA4가 항상 정답은 아닙니다. 이미 GA4를 붙여 두었다면 GA4 Data API가 가장 자연스럽고, Cloudflare를 쓰고 있다면 Cloudflare GraphQL이 관리 지점을 줄입니다. 새로 고르는 상황이라면 Plausible, Umami, GoatCounter처럼 공개 통계와 API를 염두에 둔 도구가 더 단순할 수 있습니다.

중요한 기준은 세 가지입니다.

1. 지금 이미 수집 중인 데이터가 있는가
2. 글 URL 단위로 집계값을 안정적으로 가져올 수 있는가
3. 그 값을 정적 JSON으로 배포해도 의미가 유지되는가

이 세 가지가 맞으면 운영 서버를 만들 필요가 없습니다. 맞지 않으면 도구를 더 붙이는 것보다 숫자를 공개하지 않는 편이 낫습니다. 숫자는 신뢰를 줄 수도 있지만, 설명이 안 되면 오히려 사이트를 가볍게 보이게 합니다.

가능한 구조

서버를 운영하지 않는다면 데이터 흐름은 쓰기 요청을 프론트에서 직접 처리하지 않는 쪽으로 잡아야 합니다.

방문자 브라우저
  -> 분석 도구가 방문 데이터를 집계
  -> GitHub Actions가 하루 1회 API 호출
  -> public/stats/posts.json 생성
  -> 정적 사이트가 JSON을 읽어 표시

이 구조에서는 방문자가 사이트를 볼 때 내 서버로 쓰기 요청을 보내지 않습니다. 방문 데이터 수집은 이미 쓰고 있는 분석 도구가 맡고, 사이트는 배포된 JSON을 읽기만 합니다.

Google Analytics Data API는 보고서 데이터에 프로그래매틱 방식으로 접근할 수 있고, 공식 예시에도 “지난 28일 동안 내 사이트의 상위 10개 페이지의 페이지 조회수” 같은 보고가 포함되어 있습니다. 또한 맞춤 대시보드 구축과 보고 자동화도 사용 사례로 설명합니다.

Cloudflare도 Web Analytics와 GraphQL Analytics API를 제공합니다. Cloudflare Web Analytics는 웹 페이지 성능과 방문 데이터를 이해하는 용도이고, GraphQL Analytics API는 Cloudflare 네트워크를 통과한 HTTP 요청 데이터와 제품별 분석 데이터를 쿼리할 수 있게 합니다.

Plausible은 Stats API에서 pageviews, visitors, visits 같은 지표를 JSON으로 조회할 수 있게 합니다. Umami도 pageviews, stats, active users, path별 metrics 같은 엔드포인트를 제공합니다. GoatCounter는 경로별 통계와 CSV export를 제공하고, Simple Analytics는 집계용 Stats API와 원본 export용 API를 나눠 둡니다. 도구마다 인증, 요금제, 공개 통계 정책이 다르기 때문에 “쓸 수 있다”보다 “내 사이트에 계속 붙여둘 수 있다”를 기준으로 봐야 합니다.

추천 순서

선택지를 넓히면 오히려 결정이 어려워집니다. 작은 정적 사이트라면 “가장 좋은 분석 도구”를 고르기보다, 지금 운영 흐름을 덜 흔드는 쪽부터 봅니다.

상황 먼저 볼 선택지
이미 GA4를 붙여 두었다 GA4 Data API + GitHub Actions
Cloudflare를 DNS/CDN 앞단으로 쓴다 Cloudflare Web Analytics + GraphQL
새 분석 도구를 고를 수 있다 Plausible, Umami, GoatCounter
Vercel에 배포 중이고 공개 표시는 약하다 Vercel Web Analytics로 내부 판단부터
작은 공개 통계만 빠르게 붙이고 싶다 Tinylytics, Counter.dev 같은 경량 도구

정적 글 사이트라면 1순위는 이미 붙인 분석 도구를 재사용하는 것입니다. 새 SDK를 하나 더 넣기 전에 GA4나 Cloudflare에 데이터가 이미 쌓이고 있는지 확인합니다. 데이터가 있다면 GitHub Actions가 하루 한 번 읽어 public/stats/posts.json을 갱신하는 구조가 가장 작습니다.

새 분석 도구를 고를 수 있다면 API와 공개 통계가 단순한 도구를 먼저 봅니다. Plausible, Umami, GoatCounter는 이 목적에 잘 맞습니다. 반대로 실시간 접속자, 좋아요, 사용자별 활동 기록처럼 쓰기와 식별이 필요한 기능은 이 글의 범위에서 제외하는 편이 맞습니다.

정적 JSON은 얼마나 커질까

걱정할 부분은 “나중에 JSON이 너무 커지지 않나?”입니다. 이 문제는 원본 이벤트를 저장하지 않으면 작게 유지됩니다.

나쁜 구조는 이렇습니다.

[
  { "path": "/ko/posts/a", "visitedAt": "2026-07-02T09:01:00Z" },
  { "path": "/ko/posts/a", "visitedAt": "2026-07-02T09:02:00Z" }
]

이건 방문 이벤트 로그입니다. 시간이 지날수록 계속 커집니다. 정적 사이트에 둘 데이터가 아닙니다.

좋은 구조는 집계 결과만 두는 것입니다.

{
  "generatedAt": "2026-07-02T00:00:00Z",
  "window": "last_7_days",
  "posts": {
    "/ko/posts/github-pages-seo-tradeoffs/": {
      "views": 128,
      "users": 74
    },
    "/ko/posts/static-blog-choice/": {
      "views": 92,
      "users": 51
    }
  }
}

글이 100개라도 각 글에 숫자 몇 개만 있으면 JSON은 작습니다. 매일 파일을 새로 만들면 과거 이벤트가 누적되지 않습니다. 과거 추이를 보여주고 싶다면 최근 30일 정도만 저장하거나, 월별 요약 파일로 분리하면 됩니다.

데이터 형태 커지는 속도 정적 사이트 적합성
방문 이벤트 원본 빠름 낮음
글별 누적 조회수 느림 높음
최근 7일 조회수 거의 고정 높음
일별 30일 추이 관리 가능 중간
사용자별 방문 기록 위험 낮음

GitHub Actions로 갱신하는 이유

정적 사이트에서 조회수 JSON을 직접 갱신하려면 배포 흐름에 넣는 편이 가장 단순합니다.

name: update-post-stats

on:
  schedule:
    - cron: "15 0 * * *"
  workflow_dispatch:

jobs:
  update:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: node scripts/update-post-stats.mjs
      - run: git add public/stats/posts.json
      - run: git diff --cached --quiet || git commit -m "data: update post stats"

실제 구현에서는 인증 정보가 필요합니다. GA4 Data API를 쓴다면 속성 ID와 서비스 계정 권한을 다뤄야 하고, Cloudflare GraphQL API를 쓴다면 API 토큰 범위를 좁혀야 합니다. 토큰은 저장소에 커밋하지 않고 GitHub Actions secrets에 둡니다.

이 방식의 장점은 실패가 분리된다는 점입니다. 통계 갱신이 실패해도 기존 JSON이 남아 있으면 사이트는 그대로 동작합니다. 반대로 실시간 API를 브라우저에서 직접 호출하면 토큰 노출, CORS, 속도, 실패 처리를 모두 프론트에서 떠안게 됩니다.

무엇을 보여줄까

처음부터 많은 숫자를 보여줄 필요는 없습니다. 숫자가 많아질수록 독자는 피곤해지고, 운영자는 정확도에 더 많은 설명을 붙여야 합니다.

작은 블로그라면 세 가지 중 하나면 충분합니다.

표시 방식 적합한 경우
최근 7일 인기 글 홈이나 글 목록에 흐름을 보여줄 때
글 상세의 누적 조회수 글의 사회적 증거가 필요할 때
운영자 전용 JSON 공개 표시 전 검증할 때

공개 표시 문구도 조심해야 합니다. 조회수 128이라고 쓰면 정확한 카운터처럼 보입니다. 분석 도구의 집계값이라면 최근 7일 조회 128처럼 기간을 같이 쓰는 편이 낫습니다. 숫자를 반올림해 최근 7일 100회 이상 읽힘처럼 표현하는 것도 방법입니다.

좋아요는 다르다

조회수는 외부 분석 도구가 집계한 데이터를 읽어오면 됩니다. 좋아요는 다릅니다. 사용자가 버튼을 누르는 순간 쓰기 요청이 발생합니다.

서버 없이 좋아요를 만들려면 다음 문제를 피하기 어렵습니다.

그래서 운영 서버 없이 좋아요를 붙이는 것은 추천하지 않습니다. 정말 필요하다면 GitHub Discussions, 외부 댓글 도구, Supabase, Firebase, Cloudflare Workers 같은 쓰기 가능한 서비스를 검토해야 합니다. 그 순간부터는 “서버 없이”가 아니라 “직접 운영하는 서버 없이 외부 백엔드를 사용한다”에 가깝습니다.

이 방식이 맞는 경우

이 구조는 모든 사이트에 맞지 않습니다. 하지만 다음 조건이라면 충분히 현실적입니다.

[ ] 실시간 조회수가 필요하지 않다
[ ] 숫자는 하루 1회 정도 갱신되어도 괜찮다
[ ] 방문자에게 대략적인 인기와 흐름만 보여주면 된다
[ ] 원본 이벤트를 사이트 저장소에 남기지 않는다
[ ] 통계 갱신 실패가 사이트 장애로 이어지면 안 된다

반대로 결제, 랭킹, 보상, 사용자별 기록처럼 숫자의 정확성이 제품 기능이 되는 순간에는 이 방식을 쓰면 안 됩니다. 그때는 데이터베이스와 서버 검증이 필요합니다.

결론

정적 사이트에서 조회수는 “쓰기 기능”으로 만들지 말고 “읽기 데이터”로 배포하는 편이 낫습니다. 분석 도구가 수집하고, GitHub Actions가 집계하고, 사이트는 정적 JSON을 읽습니다.

정적 글 사이트라면 먼저 GA4 또는 Cloudflare에 이미 쌓인 데이터를 확인하고, 글 URL별 views, users, last7Days 정도만 정적 JSON으로 배포합니다. 화면에는 최근 7일 100회 이상 읽힘처럼 기간과 대략값을 같이 보여줍니다.

좋아요나 실시간 접속자 수는 나중 문제로 둡니다. 그 기능은 숫자를 보여주는 문제가 아니라 쓰기 저장소와 남용 방지를 설계하는 문제입니다. 작은 사이트에서 먼저 필요한 것은 정확한 카운터가 아니라, 다시 볼 만한 글을 알아볼 수 있는 조용한 신호입니다.

참고 자료