[Next.js] 나만의 학습 블로그 만들기 #2 - 방문자 수 측정하기(feat. Firebase)

송수빈송수빈
3 min read

내용 기록 외에도 방문자 수랑 댓글 기능도 넣으려고 한다.!

이번 글에서는 방문자 수 측정 기능 구현을 적어보겠다🤭

나는 서버가 없으니 파이어베이스를 선택해줬다…!

  1. 파이어베이스 사이트에 들어가서 프로젝트를 생성해주고 Firestore Database를 클릭해주자

👉🏻 링크 클릭

  1. 클릭하면 이런 화면이 나옴

  1. 데이터베이스 만들기를 클릭

프로덕션 모드에서 시작

  1. 그러면 이런 화면이 뜬다 ! 여기서 규칙을 수정해주자

기본 파이어베이스에서 제공해주는 코드를 해석해보면

모든 경로에 대해 읽기와 쓰기 전부 금지! 안전하긴 하지만 아무것도 하지 못하기 때문에 수정을 해줘야한다.

📬 조회수 측정은 가능할 수 있도록

rules_version = '2';

service cloud.firestore {
  match /databases/{database}/documents {
    match /views/{slug} {
      allow read: if true;

      allow update: if
        request.resource.data.keys().hasOnly(['count']) &&
        request.resource.data.count is int &&
        request.resource.data.count > resource.data.count;

      allow create: if
        request.resource.data.keys().hasOnly(['count']) &&
        request.resource.data.count == 1;
    }
    match /{document=**} {
      allow read, write: if false;
    }
  }
}

이렇게 수정하고 게시를 눌러줘서 반영해줬다 !

  1. Firebase 프로젝트 클릭하면 상단 가운데 아이콘 중 </> (웹 앱 추가) 클릭해주고 하라는대로 해주면
const firebaseConfig = {
  apiKey: process.env.NEXT_PUBLIC_FIREBASE_API_KEY,
  authDomain: process.env.NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN,
  projectId: process.env.NEXT_PUBLIC_FIREBASE_PROJECT_ID,
  storageBucket: process.env.NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET,
  messagingSenderId: process.env.NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID,
  appId: process.env.NEXT_PUBLIC_FIREBASE_APP_ID,
};

이런 설정객체들을 알려주는데

firebase.js 폴더를 만들어줘서 넣어줬다.

import { initializeApp, getApps } from "firebase/app";
import { getAnalytics, isSupported } from "firebase/analytics";

const firebaseConfig = {
  apiKey: process.env.NEXT_PUBLIC_FIREBASE_API_KEY,
  authDomain: process.env.NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN,
  projectId: process.env.NEXT_PUBLIC_FIREBASE_PROJECT_ID,
  storageBucket: process.env.NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET,
  messagingSenderId: process.env.NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID,
  appId: process.env.NEXT_PUBLIC_FIREBASE_APP_ID,
};

const app =
  getApps().length === 0 ? initializeApp(firebaseConfig) : getApps()[0];

let analytics = null;
if (typeof window !== "undefined") {
  isSupported().then((yes) => {
    if (yes) {
      analytics = getAnalytics(app);
    }
  });
}

export { app };

이렇게 하면 firebase 준비는 끝났다 !!!!!

이제 vscode에서 설정해보자

incrementView.js

import { db } from "./firebase.js";
import {
  collection,
  getDocs,
  doc,
  getDoc,
  setDoc,
  updateDoc,
  increment,
} from "firebase/firestore";

export async function incrementViewCount(slug) {
  const ref = doc(db, "views", slug);
  const snap = await getDoc(ref);

  if (snap.exists()) {
    await updateDoc(ref, {
      count: increment(1),
    });
  } else {
    await setDoc(ref, {
      count: 1,
    });
  }
}

export async function getViewCount(slug) {
  const ref = doc(db, "views", slug);
  const snap = await getDoc(ref);
  return snap.exists() ? snap.data().count : 0;
}

export async function getTotalViews() {
  const snapshot = await getDocs(collection(db, "views"));
  let total = 0;
  snapshot.forEach((doc) => {
    total += doc.data().count || 0;
  });
  return total;
}

TotalView.js

import { useEffect, useState } from "react";
import { getTotalViews } from "../../lib/incrementView";

export default function TotalView() {
  const [total, setTotal] = useState(0);

  useEffect(() => {
    getTotalViews()
      .then(setTotal)
      .catch(() => setTotal(0));
  }, []);

  return (
    <div className="text-right sm:text-sm text-xs text-gray-600 dark:text-gray-400">
      조회수 | <span className="font-semibold">{total.toLocaleString()}회</span>
    </div>
  );
}

만든 컴포넌트를 _app.js에 넣어줬다

이렇게 틀은 만들었고 아직 만든 페이지가 없어서 slug를 홈에만 넣어주려고 한다.

index.js

import { useEffect } from "react";
import { incrementViewCount } from "../../lib/incrementView";
export default function Home() {
  useEffect(() => {
    incrementViewCount("home");
  }, []);

  return <div className="max-w-[940px] mx-auto"></div>;
}

설정 완료 ~~~ ! 🤭

아직은 배포를 안 해서 조회수는 증가가 안되지만 기능 자체는 잘 붙여서 뿌듯하다.

역시 파이어베이스는 프엔한테 최고다…🙌🏻

0
Subscribe to my newsletter

Read articles from 송수빈 directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

송수빈
송수빈