2026-03-11
Neon DB + Vercel 무료 플랜으로 사이드 프로젝트 운영하기
들어가며 — 왜 굳이 무료로 하는가
사이드 프로젝트를 운영하는 개발자라면 누구나 한 번쯤 이런 생각을 한다. "이거 서버비 나가는 거 아깝다." 솔직히 말하면 월 만 원도 안 되는 돈인데, 그 만 원이 아까운 게 아니라 내가 만든 거에 돈을 태우고 있다는 사실 자체가 미묘하게 스트레스다. 트래픽이 폭발하면 모를까, 하루 방문자 수가 한 손으로 셀 수 있는 수준인데 매달 결제가 나가면… 뭔가 패배한 기분이 든다.
그래서 나는 내 개인 블로그 zerry.co.kr을 철저하게 무료 플랜으로만 운영하고 있다. Vercel Hobby 플랜 + Neon 무료 PostgreSQL 조합이다. 결론부터 말하면 잘 돌아간다. 다만, "잘"의 정의가 좀 유연해야 한다. 오늘은 이 조합으로 실제로 블로그를 운영하면서 겪은 삽질들을 솔직하게 기록해 보려고 한다.
기술 스택 요약
프론트엔드/배포: Next.js + Vercel (Hobby 플랜, 무료)
데이터베이스: Neon PostgreSQL (Free Tier)
SQL 클라이언트: @neondatabase/serverless (pg vector 때문에 sql 직접사용중이고 추후 Drizzle로 변경 예정)
비용: 정확히 $0.00/월
이렇게 적어놓으면 참 깔끔해 보이는데, 현실은 좀 다르다.
삽질 1: Neon Cold Start — "빌드가 왜 터지지?"
Neon 무료 플랜의 가장 큰 특징이자 함정은 Auto-Suspend 기능이다. 5분 동안 아무 쿼리도 없으면 데이터베이스 컴퓨트가 잠든다. 깊은 잠에 빠진다. 동면에 들어간다.
이게 왜 문제냐면, Vercel에서 빌드할 때 DB 연결이 필요한 경우가 있기 때문이다. 예를 들어 빌드 타임에 DB에서 데이터를 가져와서 정적 페이지를 생성한다거나, Prisma나 Drizzle의 마이그레이션 체크가 빌드 과정에 포함되어 있다거나. 이런 상황에서 DB가 자고 있으면 이런 에러를 만나게 된다.
Error: connect ETIMEDOUT xxx.xxx.xxx.xxx:5432
at TCPConnectWrap.afterConnect [as oncomplete] (node:net:1595:16)
Build failed with exit code 1
처음 이 에러를 봤을 때는 꽤 당황했다. "아니, 어제까지 잘 됐는데?" 하면서 코드를 이리저리 뒤져봤다. 환경변수도 확인하고, 커넥션 스트링도 확인하고. 근데 문제는 코드가 아니라 DB가 자고 있었던 것이다.
해결 방법
놀랍도록 원시적이다. 그냥 다시 배포하면 된다.
첫 번째 빌드 시도에서 DB로 커넥션이 나가면, 그 요청이 Neon을 깨운다. Neon이 깨어나는 데 보통 13초 정도 걸리는데, Vercel 빌드의 타임아웃이 그보다 짧으면 실패한다. 하지만 이미 DB는 깨어났으므로 바로 재배포하면 성공한다.
이게 좀 바보 같지만, 무료 플랜의 세계에서는 이런 게 "솔루션"이다. 좀 더 엔지니어링스러운 접근을 하고 싶다면 몇 가지 방법이 있다.
빌드 스크립트에 DB 웜업 단계 추가: 빌드 전에 간단한
SELECT 1쿼리를 날려서 DB를 깨우고, 잠깐 기다린 다음 실제 빌드를 시작하는 방식Connection timeout 넉넉하게 설정: Neon의 cold start가 보통 수 초 내로 끝나니까, 커넥션 타임아웃을 1015초 정도로 설정해두면 웬만하면 버틸 수 있다
빌드 타임에 DB 의존성 줄이기: 가능하다면 빌드 시점에는 DB를 안 건드리게 구조를 바꾸는 게 근본적인 해결책이다
나는 세 번째 방법, 빌드 타임에 DB 의존성을 완전히 제거하는 걸 선택했다.
// app/blog/[slug]/page.tsx
export const revalidate = 3600; // 1시간마다 ISR
export async function generateStaticParams() {
return []; // 빌드 시 DB 호출 없음
}generateStaticParams에서 빈 배열을 반환하면 빌드 때 페이지를 하나도 안 만든다. 대신 첫 방문 시 요청이 들어오면 그때 DB를 조회해서 페이지를 생성하고 캐시한다. dynamicParams는 기본값이 true라 별도 설정 없이 동작한다.
결과적으로 빌드는 DB 상태와 무관하게 항상 성공하고, 페이지는 ISR로 1시간마다 백그라운드에서 갱신된다. Neon이 자고 있든 깨어 있든 빌드가 깨질 일이 없다.
삽질 2: Vercel Cron Jobs — "무료인데 크론이 된다고?"
Vercel Hobby 플랜에서 Cron Jobs를 쓸 수 있다는 걸 알았을 때 꽤 기뻤다. 무료 플랜에서 스케줄링이 된다니! 근데 역시나, 공짜에는 조건이 따른다.
무료 플랜 크론 제약사항
최대 100개의 크론 작업 등록 가능 — 사이드 프로젝트에는 충분하다
최소 실행 간격: 하루 1회 — 1시간마다, 5분마다 같은 건 Pro 플랜부터 가능
스케줄링 정밀도: Hourly — 이게 좀 함정인데,
0 18 * * *이라고 적어도 정확히 18:00:00에 실행되는 게 아니라 18시에서 19시 사이 아무 때나 실행될 수 있다. ±59분의 오차가 있다는 뜻이다시간대는 UTC — 한국 시간이 아니다. 한국은 UTC+9이므로 반드시 변환해야 한다
vercel.json 크론 설정 예시
내 블로그에서 실제로 쓰고 있는 크론 설정이다.
{
"crons": [{
"path": "/api/trash/purge",
"schedule": "0 18 * * *"
}, {
"path": "/api/cron/daily-stats",
"schedule": "0 0 * * *"
}]
}여기서 시간 변환이 중요하다.
0 18 * * *→ UTC 18시 = 한국 시간 새벽 3시 (18 + 9 = 27, 27 - 24 = 3). 휴지통 비우기 같은 정리 작업은 새벽에 돌리는 게 맞으니까 이렇게 설정했다.0 0 * * *→ UTC 0시 = 한국 시간 오전 9시 (0 + 9 = 9). 일간 통계 집계를 아침에 돌리는 용도다.
흔한 실수가 "한국 시간으로 새벽 3시에 돌리고 싶으니까 0 3 * * *이라고 적어야지!" 하는 건데, 이러면 UTC 3시 = 한국 시간 정오 12시에 실행된다. 생각보다 많이 헷갈리니까 항상 "내가 적는 시간 = UTC"라는 걸 기억하자.
그리고 앞서 말한 ±59분 오차 때문에, "정확히 새벽 3시에 실행되어야 하는" 크리티컬한 작업에는 적합하지 않다. 하지만 휴지통 비우기나 통계 집계 같은 건 새벽 3시든 3시 47분이든 상관없으니까, 사이드 프로젝트 용도로는 충분하다.
삽질 3: Vercel 무료 플랜 함수 제한
Vercel Hobby 플랜의 Serverless/Edge Function에도 꽤 빡빡한 제한이 있다. 실제로 운영하면서 체감한 것들을 정리하면 이렇다.
Serverless Functions
실행 시간 제한: 10초 (Pro는 60초, Enterprise는 900초)
메모리: 1024MB
번들 사이즈: 50MB (압축 상태)
Edge Functions
실행 시간 제한: 30초
메모리: 128MB
번들 사이즈: 4MB (압축 상태)
10초 제한이 생각보다 타이트할 때가 있다. 특히 Neon cold start와 결합되면 치명적이다. DB가 자고 있는 상태에서 API 요청이 들어오면, DB 깨우는 데 2~3초 + 실제 쿼리 시간이니까 남는 시간이 별로 없다. 복잡한 쿼리라도 돌리면 쉽게 타임아웃에 걸린다.
내가 쓰는 꼼수는 이렇다.
가능한 한 쿼리를 단순하게 유지한다. JOIN 3개 이상은 경계한다.
자주 조회되는 데이터는 ISR(Incremental Static Regeneration)로 캐싱해서 DB 부하를 줄인다.
Edge Function을 쓸 수 있는 건 Edge로 돌린다. 30초 제한이라 좀 더 여유가 있다. 대신 Node.js API 전체를 쓸 수 없어서 호환성 확인이 필요하다.
삽질 4: Neon 무료 플랜 스토리지와 브랜칭
Neon 무료 플랜의 스토리지 제한은 512MB이다. 텍스트 위주의 블로그 데이터라면 솔직히 꽤 오래 쓸 수 있는 양이다. 하지만 이미지를 Base64로 DB에 저장한다거나 하면 순식간에 차버릴 수 있으니 주의가 필요하다. (이미지는 Cloudinary 같은 무료 이미지 호스팅을 쓰자. 이것도 무료다.)
브랜칭 기능
Neon의 킬러 피처 중 하나가 Database Branching이다. Git 브랜치처럼 데이터베이스를 분기할 수 있다. 무료 플랜에서는 이런 제한이 있다.
브랜치 수: 10개까지 생성 가능
컴퓨트: 하나의 활성 컴퓨트 (동시에 여러 브랜치를 활성화할 수 없음)
컴퓨트 시간: 월 191.9시간 — 약 8일 정도인데, Auto-Suspend 덕분에 실제로는 훨씬 오래 쓸 수 있다 (자동으로 꺼지니까)
브랜칭이 유용한 시나리오는 스키마 마이그레이션 테스트다. 프로덕션 DB를 건드리기 전에 브랜치를 만들어서 마이그레이션을 돌려보고, 문제가 없으면 프로덕션에 적용한다. 무료인데 이런 게 되는 거 자체가 감사한 일이다.
다만, Auto-Suspend 때문에 브랜치를 전환할 때마다 cold start가 발생할 수 있다는 점은 알아두자. 개발 중에 브랜치 왔다 갔다 하면서 "어 왜 또 느리지?" 하는 순간이 꽤 온다.
무료 플랜에서 살아남기 위한 실전 팁
1년 넘게 이 조합으로 운영하면서 체득한 팁들을 정리해 본다.
1. DB 커넥션 풀링을 반드시 쓰자
Neon은 Connection Pooling 엔드포인트를 제공한다. Serverless 환경에서는 매 요청마다 새 커넥션을 맺는데, 풀링 없이는 금방 커넥션 한도에 걸린다. 커넥션 스트링에서 호스트 부분이 -pooler가 붙은 걸 쓰면 된다.
# 일반 커넥션
postgresql://user:pass@ep-xxx.region.aws.neon.tech/dbname풀링 커넥션 (이걸 쓰자)
postgresql://user:pass@ep-xxx-pooler.region.aws.neon.tech/dbname
2. 빌드 실패하면 당황하지 말고 재시도
ETIMEDOUT가 뜨면 일단 한 번 더 배포해 보자. 십중팔구 DB가 자고 있었던 거다. 자동 재시도 로직을 빌드 스크립트에 넣으면 더 좋다.
3. 크론 시간은 항상 UTC로 생각하자
이건 정말 자주 실수하는 부분이다. 나도 처음에 한국 시간으로 적어놓고 "왜 이상한 시간에 돌아가지?" 한 적이 있다. 크론 표현식 옆에 주석으로 한국 시간을 적어두는 습관을 들이면 좋다.
{
"crons": [{
"path": "/api/trash/purge",
"schedule": "0 18 * * *"
}]
}위처럼 설정하면서 머릿속으로 "18 UTC = 새벽 3시 KST"를 되뇐다.
4. 이미지는 외부 서비스에 맡기자
Neon의 512MB를 아끼려면 이미지 같은 바이너리 데이터는 DB에 넣지 말자. Cloudinary 무료 플랜이면 충분하다. 나도 블로그 이미지를 전부 Cloudinary에 올리고 URL만 DB에 저장하고 있다.
5. ISR과 캐싱을 적극 활용하자
블로그 같은 경우 글이 자주 바뀌지 않으니까, ISR로 정적 페이지를 생성해두면 DB 요청을 크게 줄일 수 있다. DB 요청이 줄면 cold start를 만날 확률도 줄고, 함수 실행 시간도 아낄 수 있고, 사용자 체감 속도도 빨라진다. 일석삼조다.
6. 에러 모니터링은 무료 도구로
Vercel의 빌트인 로그는 Hobby 플랜에서도 볼 수 있지만 보존 기간이 짧다(1시간). Sentry 무료 플랜이나 LogFlare 같은 걸 붙여두면 에러 추적이 훨씬 수월해진다. 무료 위에 무료를 쌓는 거다. 무료 만세.
무료 플랜의 숨은 장점
불만만 늘어놓은 것 같지만, 사실 이 조합의 장점도 꽤 많다.
운영 부담 제로: 서버 관리할 필요가 없다. 인프라? Vercel이랑 Neon이 다 해준다.
스케일 걱정 없음: ...트래픽이 없으니까. (자조)
꽤 괜찮은 성능: cold start만 넘기면 응답 속도가 나쁘지 않다. Neon도 쿼리 자체는 빠르다.
개발 경험이 좋다: Vercel의 프리뷰 배포, Neon의 브랜칭 등 DX가 훌륭하다. 무료인데 이 정도면 감사하다.
제약이 설계를 단단하게 만든다: 10초 타임아웃 때문에 자연스럽게 쿼리를 최적화하게 되고, 캐싱 전략을 고민하게 된다. 제약 속에서 좋은 습관이 생긴다.
실제 비용 비교
내가 같은 스펙을 유료로 쓴다면 어떨까 한번 계산해 봤다.
Vercel Pro: $20/월
Neon Launch 플랜: $19/월
합계: $39/월 (약 5만 원)
1년이면 60만 원이다. 하루 방문자 10명인 블로그에 60만 원. 물론 트래픽이 늘면 유료 전환을 고려하겠지만, 지금 단계에서는 무료로 충분하다. 그 60만 원으로 맛있는 거 먹자.
마치며 — 무료의 미학
어떤 사람들은 "그냥 만 원 내고 편하게 쓰지 왜 고생이냐"라고 할 수 있다. 맞는 말이다. 하지만 나는 이 제약 안에서 돌아가게 만드는 과정 자체가 재미있다. 한정된 리소스 안에서 최적화하고, 꼼수를 부리고, 때로는 "그냥 재배포" 같은 원시적인 해결책에 의존하는 것. 이게 사이드 프로젝트의 묘미 아닌가.
Neon + Vercel 무료 조합은 사이드 프로젝트에 정말 충분하다. 완벽하진 않지만, 돈 한 푼 안 쓰고 PostgreSQL DB가 달린 풀스택 웹앱을 전 세계에 배포할 수 있다는 것 자체가 좋은 시대에 살고 있다는 증거다.
여러분도 사이드 프로젝트에 불필요하게 돈 쓰고 있다면, 한번 무료 플랜으로 내려와 보는 건 어떨까. 삽질은 좀 하겠지만, 그 삽질이 블로그 글감이 된다. 이 글처럼.
관련 글
벡터 유사도 기반Next.js ISR + on-demand revalidation 실전기 — 캐시가 안 풀린다
ISR 캐시가 글 수정 후에도 안 풀리는 문제를 겪고, revalidatePath와 after() API로 해결한 과정을 정리한다.
80% 일치개인 블로그에 RAG 기반 AI 챗봇 구축하기
pgvector + OpenAI 임베딩 + Groq LLM으로 블로그 콘텐츠 기반 RAG 챗봇을 만든 과정을 정리했습니다.
77% 일치IndexNow 연동기 — 블로그 글 발행하면 검색엔진에 자동 알림
새 글을 발행할 때마다 수동으로 Google Search Console에서 색인 요청하는 게 귀찮아서 IndexNow API를 연동한 이야기.