06. 방문자 집계 기능 만들기
2024-06-27 오전 11시 36분
2024-06-27 오전 11시 36분
개요
해당 블로그는 Supabase를 통해
nonSQL 백엔드 서비스를 이용하여 댓글과 방문자집계를 구현 하였습니다.
그 과정을 소개해보고자 합니다.
Supabase 란?

Supabase는 오픈소스 Firebase 대안입니다.
Postgres 데이터베이스, 인증, 인스턴드 API, Edge 기능, 실시간 구독, 스토리지 및 벡터 임베딩을 지원합니다.
20개 이상의 프레임워크와 원활하게 작동하면서, 간단하게 구축을 시도할 수 있습니다.
Supabase를 왜 사용하는가?
프론트엔드 개발자로서 백엔드가 아예없이 프론트영역을 구축하는데에는 아무래도 한계가 있습니다.
사용자의 요청에 따른 서버의 응답등이 아예없이 구현하려면 구현할 수 없는 부분도 존재합니다.
supabase
는 가볍고 사용하기 쉬운 백엔드 서비스를 지원해줍니다.
가격적인 측면에서의 장점

요금도 중요합니다.
무료로 2개의 프로젝트를 지원하고, API요청에 제한이 없으며 무료사용자에게 상당히 큰 혜택을
제공 해줍니다. 사실 안 쓸 이유가 없습니다!
하지만 저희는 supabase
서비스를 사전에 설정하지 않았는데요.
이런 부분에서 약간 어려움을 느낄 수도 있지만, 직접 수동으로 적용해보겠습니다.
Supabase 사전 세팅 (INIT)
우선 홈페이지에서 프로젝트를 생성하고, Table Editor
에서 가볍게 테이블을 생성합니다.
쿼리문으로 작성하거나, 직접 제작하시면됩니다.
저는 쿼리문으로 작성해보겠습니다.
CREATE TABLE daily_visitors (
id SERIAL PRIMARY KEY,
visit_date DATE NOT NULL,
visit_count INTEGER NOT NULL DEFAULT 0,
ip_address VARCHAR(45) NOT NULL
);
간단한 일일 사용자를 기록하는 sql문을 SQL Editor
에서 작성하고, 테이블에 등록합니다.
등록된 테이블은 직접 확인해볼 수 있습니다.
이제 등록된 테이블을 다루기전에, DB API
요청시 필요한
환경 변수 값을 설정해주어야 합니다.

환경변수는 Settings -> API에서 확인할 수 있습니다.
여기서 저희가 필요한 값은 URL
과, PUBLIC ANON KEY
입니다.

이와 같이 환경변수 값을 설정합니다.
물론 next.js
에서 PUBLIC
속성을 주는경우 클라이언트/서버 양쪽에서 확인할 수 있는 문제가 있습니다.
URL
과 ANON KEY
는 민감한 정보는 아닙니다.
따라서 PUBLIC
환경변수로 사용해도 문제되지 않습니다.
저는 회원인증없이 비회원 댓글 / 비회원 조회수를 설정할 예정입니다.

Table Editor
에서 RLS
기능을 헤제 해주시기바랍니다.
RLS
는 사용자의 인증정보여부를 확인하고 CRUD기능에 제약을 주는데요,
제가 사용할 데이터에는 민감한 정보가 없고 제약을 안줄 생각이므로
RLS
는 따로 사용하지 않습니다.
이제 마지막으로 supabase
를 클라이언트측에서 사용할 수 있게 세팅하겠습니다.
npm i @supabase/supabase-js
부터 적용합니다.
import { createClient } from '@supabase/supabase-js';
const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL as string;
const supabaseAnonKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY as string;
export const supabase = createClient(supabaseUrl, supabaseAnonKey);
이제 초기 INIT
세팅은 완료하였습니다. supabase
요청이 필요한
클라이언트에서 요청문을 작성해서 직접 사용해보겠습니다.
방문자 집계 구상하기
- 현재 날짜 데이터를 쿠키로 발급 (만료기한
24H
)
- 쿠키의 정보와 현재 날짜가 같다면 방문자 수 + 1
- 쿠키의 정보와 현재 날짜가 다르다면 신규
Row
생성, 쿠키 삭제
- 쿠키가 삭제되고 신규
row
가 생성 된 경우, 쿠키 재발급(만료기한 24H
), 방문자 수 +1
- 쿠키가 이미 있을경우 방문자 수 + 0
위의 조건에 해당하는 라이브러리 함수, visitorCount.ts
를 작성해보겠습니다.
import dayjs from 'dayjs';
import Cookies from 'js-cookie';
export const getCookie = () => {
const cookie = Cookies.get('currentDate');
if (!cookie) {
const currentDate = dayjs(Date.now()).format('YYYY-MM-DD');
Cookies.set('currentDate', currentDate, { expires: 1 });
}
};
우선 간단하게 쿠키를 만들어주는 함수를 만듭니다.
이 함수는 홈페이지 접속시 호출하면
문제없이 사용자에게 쿠키를 발급할 수 있습니다.
이제 쿠키 값을 기반으로 카운트를 업데이트하는 함수를 만들어 보겠습니다.
export const updateVisitCount = async () => {
const today = dayjs(Date.now()).format('YYYY-MM-DD');
const { data, error } = await supabase.from('daily_visitors').select('*').eq('visit_date', today).single();
if (error) {
const { data: TableCreateData, error: TableCreateError } = await supabase
.from('daily_visitors')
.insert({ visit_date: today, visit_count: 1 })
.select()
.single();
if (TableCreateData) console.log('날짜 갱신');
if (TableCreateError) return console.error(TableCreateError);
}
const { data: updatedData, error: updateError } = await supabase
.from('daily_visitors')
.update({ visit_count: data.visit_count + 1 })
.eq('visit_date', today)
.select()
.single();
if (!updateError) return updatedData.visit_count;
if (updateError) return 0;
};
supabase
에서
from
테이블 명
update
업데이트 할 내용
eq
조건
select
조회
single
쿼리 결과가 하나의 레코드가 되도록 보장
를 의미합니다. 자신의 환경에 맞게 잘 이용해보세요.
조건을 걸어 우선 테이블내에 데이터가 존재하는지 확인합니다.
데이터가 없다면 today
, 오늘 날짜에 해당하는 신규 row
를 만듭니다.
그리고 만들어진 데이터에 visit_count
를 늘립니다.
이제 이미 쿠키가 존재할경우 실행할 함수를 만듭니다.
export const viewVisitCount = async () => {
const today = dayjs(Date.now()).format('YYYY-MM-DD');
const cookie = Cookies.get('currentDate');
if (today !== cookie) {
Cookies.remove('currentDate');
Cookies.set('currentDate', today, { expires: 1 });
const data = await updateVisitCount();
return data;
}
const { data, error } = await supabase.from('daily_visitors').select('*').eq('visit_date', today).single();
if (error) {
const { data, error } = await supabase.from('daily_visitors').insert({ visit_date: today, visit_count: 1 }).select().single();
data && console.log('날짜 갱신');
error && console.error(error);
}
if (data) {
const { data: updatedData, error: updateError } = await supabase.from('daily_visitors').select().eq('visit_date', today).single();
if (!updateError) return updatedData.visit_count;
if (updateError) return 0;
}
};
현재 데이터와 쿠키의 정보가 다르다면, 신규 쿠키를 발급해주고 visit_count
를 1로 초기화합니다.
현재 데이터와 쿠키의 정보가 같다면, 현재 집계수를 보여줍니다.
GET
요청이 실패할 경우, 집계수를 1로 업데이트해서 보여줍니다.
만약의 발생할 에러를 위한 구문입니다.
이제 위의 데이터를 기반으로 클라이언트 측의 요청을 작성해봅시다.
클라이언트 측 코드 구상하기
components/header/UserCount.tsx
const [visitCount, setVisitCount] = useState('1');
const [focus, setFocus] = useState(false);
useEffectOnce(() => {
const countInit = async (type: string) => {
if (type === 'UPDATE') {
getCookie();
const data = await updateVisitCount();
setVisitCount(data);
}
if (type === 'VIEW') {
const data = await viewVisitCount();
setVisitCount(data);
}
};
const cookie = Cookies.get('currentDate');
if (!cookie) {
countInit('UPDATE');
} else {
countInit('VIEW');
}
});
아주 간단한 구성입니다. useEffectOnce
는 react-use
라이브러리에서
제공해주는 함수로서, useEffect
를 한번만 작동할 것을 보장해줍니다.
접속시 쿠키가 없다면 updateVisitCount()
를 호출해서 사용자 집계를 늘리거나
신규 테이블을 생성하게 합니다.
접속시 쿠키가 있다면 viewVisitCount()
를 호출해서 사용자 집계를
가져오게 합니다.
이로서 문제없이 작동하는 방문자 집계 기능이 완성되었습니다.

다음 포스트에서는 비회원 댓글기능을 만들어보겠습니다.
댓글은 포스팅에 도움이됩니다. 적극적인 의견 감사드립니다.