Supabase Cron Job 구축하기

Notion 글을 저장하는 Supabase Cron Job을 만들어 보았습니다.

Supabase Cron Job 구축하기

저는 지금까지 여러 블로그를 만들어 보았습니다. velog, 티스토리, 네이버 블로그처럼 글을 작성할 수 있는 플랫폼은 이미 많지만, 새로운 기술을 직접 적용하고 검증해보기에는 블로그만큼 좋은 프로젝트가 없다고 생각했습니다.

블로그는 한 번 만들고 끝나는 코드가 아니라, 실제로 계속 운영하고 개선할 수 있는 서비스이기 때문입니다.

하지만 블로그를 꾸준히 운영하고 콘텐츠를 작성하는 과정에는 생각보다 많은 불편함이 있었습니다. AWS 서버 비용, 콘텐츠 관리 방식, 글을 작성하는 텍스트 에디터 등 여러 환경적인 문제가 있었고, 그때마다 블로그를 새로 만들거나 구조를 수정하게 되었습니다.

그렇게 여러 시행착오를 거치며, 제가 원하는 블로그의 조건을 정리할 수 있었습니다.

  1. 블로그 운영에 도메인 외의 비용이 들지 않아야 한다.
  2. CMS(Content Management System)로 내게 익숙한 에디터를 활용할 수 있어야 한다.
  3. 새로운 기술을 쉽게 적용할 수 있도록** 커스텀이 쉬워야 한다**.

위 조건에 의해 저는 Notion과 Supabase를 활용하여 블로그를 직접 개발하게 되었고, 그 과정 속에 기술적인 흔적들을 기록하고자 합니다.

Supabase란?

Supabase는 쉽게 말해 Firebase의 오픈소스 대안으로 자주 설명되는 Backend as a Service(BaaS) 플랫폼입니다. 즉, AWS에 직접 서버를 띄우지 않아도, 백엔드 기능을 사용할 수 있게 해주는 서비스입니다.

Supabase Cloud는 AWS 같은 클라우드 인프라 위에서 동작하지만, 인프라 생성과 운영은 Supabase가 대신 관리합니다. 사용자는 직접 서버를 띄우거나 관리하지 않고, Supabase가 제공하는 데이터베이스, 인증, 스토리지 등의 백엔드 기능을 사용하는 구조입니다.

2-tier? 3-tier?

Supabase는 PostgreSQL을 기반으로 제공되기 때문에 단순히 데이터베이스처럼 사용한다고 생각하기 쉽습니다. 하지만 프론트엔드가 데이터베이스에 직접 접근하는 2-tier 구조라기보다는, Supabase API 계층을 거쳐 데이터베이스에 접근하는 3-tier 구조에 가깝습니다. 사실 여기에 캐싱 계층까지 추가하면 구조는 더 확장되기에 n-tier 구조로 볼 수 있습니다.

Notion → Supabase

Notion API를 이용하면 Notion에 작성한 글을 바로 가져와 렌더링할 수 있습니다. 하지만 저는 Notion의 Post 데이터를 Supabase에 저장한 뒤, 블로그에서는 Supabase의 데이터를 기준으로 렌더링하는 방식을 선택했습니다.

이유는 단순합니다. Notion은 글을 작성하기에는 편리하지만, 블로그 서비스에서 사용할 데이터를 안정적으로 제공하기 위한 저장소로는 한계가 있다고 생각했기 때문입니다.

먼저 안정성 측면에서, Notion의 이미지 URL은 presigned URL 형태로 제공되어 시간이 지나면 변경될 수 있습니다. 또한 페이지를 렌더링할 때마다 Notion API에 의존하면 Notion API의 응답 속도나 장애가 블로그에 직접적인 영향을 줄 수 있습니다.

유지보수성확장성 측면에서도 Supabase를 중간 데이터 계층으로 두는 편이 유리했습니다. 제목, slug, 태그, 썸네일, 본문, 작성일처럼 블로그에 필요한 데이터 형태로 정리해둘 수 있고, 이후 검색, 태그 필터링, 조회수, 추천 글, 캐싱 같은 기능을 추가하기도 쉬워집니다.

즉, Notion은 콘텐츠를 작성하는 CMS로 사용하고, Supabase는 블로그 서비스에서 사용할 데이터를 안정적으로 관리하는 저장소로 분리했습니다.

Cron Job 구축하기

Notion 데이터를 Supabase에 저장하는 방법은 여러 가지가 있습니다. 저는 그중에서도 주기적으로 Notion 데이터를 가져와 Supabase에 반영하는 동기화 작업이 필요하다고 판단했습니다.

이 동기화 작업은 Vercel Cron, GitHub Actions, Supabase Edge Functions 등 다양한 환경에서 실행할 수 있습니다. Next.js 애플리케이션을 Vercel에 배포할 예정이기 때문에 Vercel Cron을 사용하는 것도 자연스러운 선택이었습니다. 하지만 블로그를 렌더링하는 애플리케이션과 데이터 동기화 작업이 같은 책임 안에 섞이는 것은 피하고 싶었습니다.

GitHub Actions를 사용하는 방법도 가능하지만, 이 경우 동기화 작업이 외부 CI 환경에 의존하게 됩니다. 저는 Notion 데이터를 저장하는 대상이 Supabase인 만큼, 데이터 동기화 로직도 Supabase 안에서 관리하는 편이 더 일관된 구조라고 판단했습니다.

따라서 Supabase Edge Functions와 Cron을 사용해 Notion 데이터를 주기적으로 가져오고, 이를 Supabase 데이터베이스에 저장하는 구조로 결정했습니다.

Supabase CLI 활용하기

Supabase는 웹 대시보드에서도 테이블과 컬럼을 직접 수정할 수 있습니다. 하지만 저는 CLI를 이용해 마이그레이션 파일로 데이터베이스 스키마를 관리하는 방식을 선호합니다.

웹에서 직접 수정하는 방식은 간편하지만, 시간이 지나면 무엇을 언제 변경했는지 추적하기 어려울 수 있습니다. 반면 마이그레이션 파일은 스키마 변경 사항을 코드로 남길 수 있어 Git으로 이력을 관리할 수 있고, 다른 환경에서도 같은 구조를 재현하기 쉽습니다.

또한 데이터베이스 구조가 코드로 관리되기 때문에 AI Agent와 함께 작업하기에도 좋습니다. 현재 스키마를 기반으로 변경 사항을 제안받거나, 새로운 마이그레이션 파일을 작성하는 식으로 활용할 수 있기 때문입니다.

무엇보다 로컬 환경에서 먼저 마이그레이션을 적용하고 테스트할 수 있어, 실제 프로젝트에 반영하기 전에 변경 사항을 안전하게 검증할 수 있습니다.

명령어 소개

아래 명령어를 참고하여 AI와 함께 쉽게 구축할 수 있습니다. 참고로 로컬에서 Supabase를 실행하려면 Docker가 필요합니다.

bash
# Local Project에 Supabase 추가
supabase init

# 로그인
supabase login

# 프로젝트 ID 확인하기 위한 리스트 보기
supabase projects list

# 현재 프로젝트와 원격 Supabase 프로젝트 연결
supabase link --project-ref <your-project-ref>

# Local Supabase 실행하기
supabase start

# 마이그레이션 파일 생성
supabase migration new <migration-name>

# Local DB에 마이그레이션 적용
supabase migration up

# Local DB 초기화 후 마이그레이션 다시 적용
supabase db reset

# Local 말고 원격 Supabase DB에 반영하기
supabase db push

Edge Function 만들기

Edge Function은 Supabase에서 제공하는 서버리스 함수입니다. 별도의 서버를 직접 배포하지 않아도, 특정 URL로 요청을 보내면 작성한 함수를 실행할 수 있습니다. 저는 Notion에서 글 데이터를 가져와 Supabase에 저장하는 동기화 로직을 Edge Function으로 구현했습니다. 이렇게 하면 Notion 데이터를 동기화하는 작업을 하나의 API처럼 실행할 수 있습니다.

즉, Edge Function을 만들면 다음과 같이 호출 가능한 REST API 엔드포인트가 생성됩니다.

아래 명령어를 사용하면 Docker 기반 로컬 환경을 거치지 않고 Supabase API를 통해 Edge Function을 바로 배포할 수 있습니다. 또한 --no-verify-jwt 옵션을 사용하면 해당 Function을 호출할 때 JWT 인증을 요구하지 않도록 설정할 수 있습니다.

bash
supabase functions deploy sync-notion-posts --use-api --no-verify-jwt

다만 --no-verify-jwt를 사용하면 인증 없이도 Function을 호출할 수 있으므로, 외부에 공개되는 API라면 별도의 secret key 검증이나 호출 권한 제어를 추가하는 것이 좋습니다.

Cron Job 만들기

먼저 Supabase Dashboard → Database → Extensions에서 pg_cron pg_net두 가지 활성화해주어야 합니다. 그 후 위의 방식처럼 다시 Cron Job 관련 된 마이그레이션 파일을 만들어주면 됩니다.

최적화하기

Notion의 모든 글을 매번 Supabase에 업데이트할 필요는 없습니다. 이미 저장된 글까지 매번 다시 가져와 덮어쓰면 불필요한 API 호출과 데이터베이스 작업이 발생하기 때문입니다.

그래서 저는 Notion의 last_edited_time을 기준으로 Supabase에 저장된 edit_time과 비교하고, 변경된 글만 업데이트하도록 동기화 로직을 구성했습니다. 새로 추가된 글은 insert하고, 기존 글 중 수정 시간이 달라진 글만 update하는 방식입니다.

또한 Edge Function 실행 결과에 어떤 글이 새로 추가되었고, 어떤 글이 수정되었는지 로그를 남기도록 했습니다. 이를 통해 Cron Job이 정상적으로 실행되었는지, Notion의 변경 사항이 Supabase에 잘 반영되었는지 쉽게 확인할 수 있도록 했습니다.

마무리

이번 글에서는 Notion을 CMS로 사용하고, Supabase를 데이터 저장소로 활용해 블로그 콘텐츠를 동기화하는 구조를 정리했습니다.

단순히 글을 렌더링하는 데서 끝나지 않고, 안정성, 유지보수성, 확장성을 고려해 Notion과 Supabase의 역할을 분리하고자 했습니다. 또한 Edge Function과 Cron Job을 활용해 동기화 작업을 자동화하고, 변경된 글만 업데이트하도록 최적화했습니다.

앞으로는 이 구조 위에 렌더링 방식, 검색, 태그 필터링, 캐싱, 추천 글 같은 기능을 추가하며 블로그를 계속 개선해볼 예정입니다.