템플렛으로 시작한 블로그를 게시글 조회수 카운트 기능을 넣어 고도화시키도록 하겠다.
Vercel에서 해당 리포지토리 배포
Vercel에 배포하게 되면 Vercel에서 DB를 사용할 수 있다. 이 기능을 통해 조회수를 관리할 것이다.
데이터베이스 선택
대시보드의 Storage 영역에서 데이터베이스로 Neon 선택한다.
원래는 PostgresSQL로 하려했느나 공식문서를 참고한 결과, PostgresSQL는 Neon으로 통합되었다고 한다.
따라서 다음과 같은 이유로 Neon을 사용하였다.
[기존 Vercel Postgres 저장소가 없는 경우 Neon Marketplace 통합을 사용하여 새 Postgres 저장소를 만들 수 있습니다. 기존 Vercel Postgres 저장소는 2024년 12월 첫째 주부터 Neon Serverless Postgres 제품 통합 으로 자동 전환 되며 2025년 1월 말까지 완료될 예정입니다.]
Next.js와 DB 연결
DB가 생성되면 어떤 프레임워크로 DB를 연결할지 선택한다. Next.js, Svelte, Astro, SolidStart를 지원하고 있다.
해당 설정을 마치면 리포지토리와 연결이 완료된다.
1. Vercel CLI 설치하기
Vercel PostgreSQL과 연결하기 위해 Vercel CLI를 사용한다. 아래의 명령어로 Vercel CLI를 설치해준다.
pnpm i -g vercel
2. Vercel 연결하기
vercel link
명령어를 입력하고 세팅을 위한 답변을 선택한다. 선택을 다 하면 Vercel DB와 해당 로컬 레포지토리가 연결된 것이다!
3. 환경변수 설정
env 값이 있어야 실제로 쿼리를 날릴 수 있기 때문에 해당 명령어를 통해 환경변수를 로컬에 만들어준다.
vercel env pull .env.development.local
.env.development.local
파일이 생성된 후 확인해보면 다음과 같이 모든 환경변수가 알아서 생성되어 있는 것을 확인할 수 있다. 이렇게 생성된 env 값으로 쿼리를 생성할 준비가 된 것이다!
# Created by Vercel CLI
DATABASE_URL=""
DATABASE_URL_UNPOOLED=""
PGDATABASE="neondb"
PGHOST=""
PGHOST_UNPOOLED=""
PGPASSWORD=""
PGUSER="neondb_owner"
POSTGRES_DATABASE="neondb"
POSTGRES_HOST=""
POSTGRES_PASSWORD=""
POSTGRES_PRISMA_URL=""
POSTGRES_URL=""
POSTGRES_URL_NON_POOLING=""
POSTGRES_URL_NO_SSL=""
POSTGRES_USER="neondb_owner"
이렇게 생성된 .env.development.local 파일은 .gitignore에 추가해야 한다.
4. Neon 패키지 설치하기
npm install @neondatabase/serverless
5. DB table 생성하기
Neon SQL Editor 에서 테이블을 생성해준다.
-
slug와 count 칼럼을 가진 views 테이블 생성
CREATE TABLE views ( slug varchar(255) primary key, count integer not null default 0 );
6. 조회수 쿼리문 작성
그리고 DB를 연결해서 조회수 값을 불러오는 코드와 조회수를 업데이트하는 코드를 작성해준다.
-
DB 연결 후 조회수 데이터 가져오는 코드 작성
// app/queries/db.ts // serverless neon 연결 const sql = neon(`${process.env.DATABASE_URL}`); // 조회수 데이터 export async function getViewsCount(): Promise< { slug: string; count: number }[] > { if (!process.env.DATABASE_URL) { return []; } const rows = await sql("SELECT slug, count FROM views"); return rows.map((row: { slug: string; count: number }) => ({ slug: row.slug, count: row.count, })); }
-
view 테이블에 조회수 업데이트 하는 쿼리 작성
// app/queries/db.ts // 조회수 증가 const incrementView = async (slug: string) => { if (!process.env.DATABASE_URL) { throw new Error("DATABASE_URL is not defined"); } // 'views' 테이블에 slug를 삽입하거나 count를 업데이트 await sql( ` INSERT INTO views (slug, count) VALUES ($1, 1) ON CONFLICT (slug) DO UPDATE SET count = views.count + 1 `, [slug] ); };
이렇게까지만 적용하게 되면, 강력 새로고침으로 리프레시를 해야 조회수가 업데이트 된다. 강력 새로고침에 종속된 상태를 풀기 위해서 noStore
개념을 활용해준다.
import { unstable_noStore as noStore } from "next/cache";
noStore는 Next.js에서 제공하는 stable하지 않는 함수를 가져다 쓰면 된다. getViewsCount와 incrementView 각 영역에 넣어준다.
"use server";
import { unstable_noStore as noStore } from "next/cache";
export async function getViewsCount(): Promise<
{ slug: string; count: number }[]
> {
// ...생략
noStore();
}
// 조회수 증가
export const incrementView = async (slug: string) => {
noStore();
// ...생략
};
이렇게 적용하게 되면, 캐시가 되지 않고 기본적인 새로고침만 해도 조회수가 올라간다.
7. 조회수 영역 만들기
조회수를 ViewCount 컴포넌트로 렌더링한다.
또한, 해당 프로젝트는 PPR(Partial Prerendering-Rendering)을 적용했기 때문에 static, dynamic한 부분을 구분지어야 하는 작업이 필요하다.
조회수 부분을 동적으로 채워야하는데, 그러기 위해 Suspense로 감싸줘서 PPR으로 렌더링할 영역으로 만든다.
// app/blog/[slug]
<Suspense>
<ViewCount slug={post.slug} />
</Suspense>
// app/components/view-count.tsx
import { getViewsCount, incrementView } from "queries/db";
type Props = {
slug: string;
};
export const ViewCount = async ({ slug }: Props) => {
// 조회수 증가 후
await incrementView(slug);
// 조회수 가져오기
const views = await getViewsCount();
const view = views.find((view) => view.slug === slug);
const count = view ? view.count : 0;
return (
<p className="text-sm text-neutral-600 dark:text-neutral-400">
{count.toLocaleString()} views
</p>
);
};
html 속성에서 확인해보면 Suspense가 남아있던 흔적이 있고, 동적인 요소로 채워지고 있다는 것을 확인할 수 있다.
이렇게 적용하게 되면, 해당 영역은 동적인 영역이 돼서 다른 static 영역보다 조금 느리게 렌더링 된다! 🌀🌀
Conclusion
Serverless DB로 조회수 기능 구현에 대한 로직을 간단하게 정리해보자면,
-
조회수 영역을 만들어줌
-
조회수 숫자를 데이터베이스로부터 가져오기 위해서 Vercel과 프로젝트를 연결해줌
-
Vercel 내에서 DB를 만들고 그 DB와 Vercel을 연결한 다음 로컬의 환경변수들을 pull 받아옴
-
DB table을 만들어줌
-
Neon 패키지 설치하고 Query를 날려서 실제로 데이터베이스를 조회하고 업데이트 해줌
-
stable하지 않은 기능을 활용해서 캐시를 해제해주는 기능까지 적용해서 조회수 업데이트 기능을 개선함