‹ 블로그

2026. 10. 13 · 서비스 만들기 · React · Supabase · 템플릿

관리자 웹을 템플릿으로 만든 이유 — 두 서비스에 적용해 보니

서비스마다 관리자 화면을 새로 만들지 않도록, 인증·권한·데이터 계층·공통 UI 를 묶은 React 템플릿을 만들었습니다. 담벼락 관리자와 봉봉 통합 관제 Admin 두 곳에 적용하며 무엇이 그대로 쓰였고 무엇을 더했는지 정리합니다.

관리자 화면은 서비스마다 비슷합니다. 로그인, 역할별 메뉴, 목록·검색·상세·수정, 감사 로그. 매번 처음부터 만들면 시간이 들고 보안 실수도 반복됩니다.

기능은 모듈로 붙인다

각 기능은 src/features/<이름>/ 에 메뉴·라우트·대시보드 위젯을 가진 모듈로 만들고, 한 파일에 등록만 하면 사이드바와 권한 가드가 자동으로 붙습니다. 필요 없는 공통 기능도 등록만 빼면 됩니다.

메뉴 숨김과 라우트 가드는 화면 편의일 뿐, 데이터 보호는 RLS 가 한다.

보안 경계

관리자 여부는 프론트의 메타데이터가 아니라 DB 의 권한 조회(RPC)로 판단하고, 결과가 없으면 거부합니다. 브라우저 번들에 service_role 같은 비밀 키가 들어가면 빌드가 실패하도록 검사 단계를 넣었습니다.

템플릿을 가져다 쓰는 방법

포크나 서브모듈 대신 템플릿 저장소를 복사해 새 저장소로 시작하고, 원본은 원격 template 으로 연결해 둡니다. 템플릿의 공통 코드(레이아웃·공통 컴포넌트·인증·권한)는 되도록 손대지 않고, 서비스 기능은 새 모듈 폴더에만 씁니다. 그래야 템플릿이 고쳐졌을 때 merge 가 충돌 없이 됩니다.

git remote rename origin template   # 복사해 온 템플릿 원본
git remote add origin <새 저장소>

# 템플릿이 바뀌면
git fetch template
git merge template/main

적용 1 — 담벼락 관리자

첫 적용은 텍스트 커뮤니티 '담벼락'의 운영자용 웹입니다. 템플릿에서 시작해 약 일주일 동안 기능 모듈 4개를 붙였습니다.

- 담벼락 운영: 신고 대기열, 게시글·댓글 숨김과 복구, 회원 경고·정지, 스팸 패턴과 도배 기준, 공지·오늘의 글감

- 배경 음악: 음원 업로드와 활성 곡 수 제한

- 오늘의 한마디: 문장 관리, 일일 노출과 이력

- 문의: 답변 저장 후 회원에게 답변 메일 발송, 발송 상태 확인

사용자 웹과 같은 Supabase 스키마를 쓰기 때문에 마이그레이션은 사용자 웹 저장소 한 곳에서만 관리하고, 관리자 웹은 RLS 와 RPC 를 호출하는 쪽에만 집중했습니다. 로그인은 Google 버튼(Identity Services)으로 바꿨는데, 인증 어댑터가 분리되어 있어 다른 화면은 건드리지 않았습니다.

적용 2 — 봉봉 통합 관제 Admin

두 번째는 성격이 전혀 다른 관리자입니다. 서비스가 늘면서 여러 Google 계정에 Vercel·Supabase·Cloudflare 무료 플랜이 흩어졌고, 어느 계정에 자리가 남았는지, 어느 서비스가 죽었는지를 계정마다 로그인해서 확인해야 했습니다. 이걸 한 화면으로 모으는 관제 앱을 같은 템플릿으로 만들었습니다.

템플릿에서 바꾼 곳은 메뉴 분류 두 개(관제, 서비스·리소스)와 모듈 등록 한 줄 정도이고, 나머지는 ops 모듈 하나에 들어갑니다. 서비스, 공급자 계정, 리소스 매핑, Health Check, 사용량·한도, 알림, 예약 작업, 배정 추천 화면이 템플릿의 표·필터·폼·확인 다이얼로그를 그대로 씁니다.

봉봉 통합 관제 Admin 의 사용량·한도 화면 — 템플릿의 표·필터 컴포넌트를 그대로 사용 (mock 데이터)
봉봉 통합 관제 Admin 의 사용량·한도 화면 — 템플릿의 표·필터 컴포넌트를 그대로 사용 (mock 데이터)

템플릿에 없던 것: 서버 쪽 작업

담벼락 관리자는 대부분 DB 의 RLS·RPC 로 끝났지만, 관제 앱은 공급자 API 토큰을 다뤄야 해서 서버가 필요했습니다. 템플릿 문서에 '민감한 작업은 Edge Function 에서 권한을 다시 확인한다'는 패턴만 적혀 있었고, 이번에 그 패턴을 실제로 만들었습니다.

- 토큰은 Edge Function 이 공급자 API 로 먼저 검증한 뒤 Supabase Vault 에 암호화해 두고, 화면과 테이블에는 끝 4자리만 남깁니다.

- 호출한 사용자의 역할을 서버에서 다시 확인하고, 모든 작업을 감사 로그에 남깁니다.

- pg_cron 이 5분마다 수집을 부르고, 작업 잠금과 재시도 상한으로 중복 실행을 막습니다.

- 조회에 실패하면 사용량을 0 으로 적지 않고 연결 오류로 표시합니다. '모르는 값'과 '0'을 구분하는 것이 관제 화면에서 가장 중요했습니다.

템플릿 회귀 테스트를 기준으로 삼았다

새 기능을 붙이는 동안 템플릿이 원래 갖고 있던 단위 테스트 89개와 E2E 31개가 그대로 통과하는 것을 완료 조건으로 잡았습니다. 실제로 mock 역할에 새 역할을 추가했다가 템플릿의 '역할·권한 매트릭스' 테스트가 깨졌고, mock 역할은 템플릿 구성을 유지하고 실제 DB 에만 새 역할을 두는 쪽으로 되돌렸습니다. 템플릿 테스트가 '공통 코드를 건드렸다'는 신호 역할을 해 준 셈입니다.

관제 모듈에는 화면·권한·오래된 데이터 판정·배정 추천 테스트와 데스크톱·모바일 E2E 를 더해 단위 104개, E2E 37개가 됐습니다.

두 번 써 보고 알게 된 것

- 기능을 모듈 폴더로 나눈 구조 덕분에 두 앱 모두 템플릿 공통 코드를 거의 고치지 않았습니다. 템플릿 업데이트를 merge 로 받을 수 있는 이유입니다.

- 반대로 '서버 함수 + 권한 재검증' 은 두 앱이 각자 만들었습니다. 다음 버전 템플릿에 예시 Edge Function 으로 넣을 후보입니다.

- Node 24 에서는 템플릿 테스트 일부가 jsdom 과 fetch 의 AbortSignal 충돌로 실패했습니다. 관제 앱에서 테스트 설정만 고쳐 우회했고, 이 수정도 템플릿으로 되돌려 넣을 목록에 올려 두었습니다.

템플릿은 처음 만들 때보다 두 번째 서비스에 적용할 때 진짜 모양이 드러난다.

관련 프로젝트관리자 웹 템플릿 ›