CPATOOLS 만들기 — 인프라 구축 3일 회고노트
Astro + Vercel + Supabase로 회계사 도구 허브를 만들면서 막힌 곳들. KOE205 지옥, env 캐시 함정, 본문 mono 실패, 그리고 의외의 닉네임 결정까지.
CPATOOLS 사이트 본체를 3일에 걸쳐 만들었다. Claude Code로 페어 프로그래밍하면서 한 거라 빠르긴 했는데, 그래도 사람이 사람인지라 막힌 곳들이 있었다. 다음에 비슷한 거 만들 때 같은 함정에 안 빠지게 정리해둔다.
결정한 스택
- Astro 6 (Content Collections, MDX) — 정적 사이트 + 블로그 + 도구 카탈로그
- Vercel 호스팅 (
cpatools.co.kr도메인) - Supabase Northeast Asia(Seoul) 리전 — 댓글 + Google OAuth
- Tailwind v4 + Pretendard + JetBrains Mono — 디자인 토큰
- GitHub Actions — Obsidian vault → 사이트 콘텐츠 싱크 (계획)
전부 무료 플랜으로 굴러간다. 도메인비만 든다.
함정 1: Vercel env 캐시
처음에 Vercel에 프로젝트 만들고 나중에 환경변수(PUBLIC_SUPABASE_URL, PUBLIC_SUPABASE_ANON_KEY)를 추가했더니 댓글 위젯에서 isSupabaseConfigured가 계속 false로 떨어졌다. Astro/Vite는 import.meta.env.PUBLIC_*을 빌드 타임에 string으로 치환하는데, env 추가 전에 빌드된 결과물엔 undefined로 박혀있어서 캐시된 빌드 쓰면 새 env가 안 들어간다.
해결은 두 가지:
- Redeploy 시 “Use existing Build Cache” 체크 해제
- 더 확실: 프로젝트 삭제하고 다시 import해서 첫 빌드 전에 env를 미리 등록
결국 두 번째 방법으로 정리했다. 같은 실수 두 번은 안 하려면 새 프로젝트 만들 때 env부터 등록 → 그 다음 Deploy.
함정 2: Kakao OAuth KOE205 지옥
가장 시간을 빼앗긴 부분. Supabase의 빌트인 Kakao provider는 OAuth scope에 account_email이 강제로 포함된다. 그런데 카카오 개인 개발자 앱은 “카카오계정(이메일)” 동의항목을 “필수 동의”는 비즈 앱 전환(사업자등록증 심사) 후에만 가능하다. “선택 동의”도 일부 계정에서 막혀있다.
그래서 일어나는 일:
- 유저가 Kakao 로그인 클릭
- Supabase가 OAuth URL에
scope=account_email profile_image profile_nickname붙여서 카카오로 보냄 - 카카오: “당신 앱은
account_email동의항목을 설정하지 않았는데 요청했네? KOE205 잘못된 요청” → 거부
우회를 시도했다.
시도 1: Supabase의 external_kakao_email_optional: true
Management API로 토글했지만 효과 없음. 이 옵션은 필수 vs 선택 표시만 바꾸고 scope 자체는 그대로 보낸다.
시도 2: 클라이언트에서 signInWithOAuth({ scopes: 'profile_nickname profile_image' })
Supabase JS의 options.scopes를 명시했더니, 결과 OAuth URL의 scope가 account_email profile_image profile_nickname profile_nickname profile_image. append만 되고 빌트인 default를 replace 못 한다.
시도 3: Custom OAuth provider로 우회
Supabase의 custom:kakao provider를 Management API로 등록하면 scope를 직접 명시할 수 있다. 가능은 한데 작업량이 생각보다 컸다.
결론
Kakao 제거. Google만 유지. 회계사 타겟에 카카오 로그인이 필요한 건 맞지만, 비즈 앱 전환 안 한 채로 우회하느라 코드를 꼬는 것보단 깔끔하다. 사업자등록 시점에 다시 추가할 백로그 항목으로 남김.
교훈: Supabase 빌트인 OAuth provider는 scope 커스터마이징 불가. Kakao처럼 동의항목 정책이 까다로운 provider는 처음부터 Custom OAuth로 가는 게 낫거나 — 아니면 그냥 빼는 게 시간 절약.
함정 3: 본문 모노스페이스 폰트
디자인을 “장부 + 테크” 메타포로 정한 뒤 — 제목 + 라벨을 JetBrains Mono로 깔았다. 그 김에 본문도 mono로 가면 더 강렬하지 않을까 싶어서 시도했다.
- 영문은 JetBrains Mono
- 한글은 Nanum Gothic Coding (Google Fonts) fallback
블로그 본문, AI 도구 설명, 댓글까지 전부 mono. 30초 보면 나름 멋있다.
근데 3분 읽으면 눈이 피로하다. 한글 등폭 글꼴은 자모 간격이 어색하고, 줄바꿈 위치가 가독성을 깬다. 회계사가 부가세 신고 체크리스트 글을 읽다가 중간에 닫을 거다.
좌우 2단으로 같은 글을 Pretendard vs Mono로 렌더해서 비교해보니 답이 명확. Pretendard 압승. 본문은 sans, 제목/라벨/숫자만 mono로 하는 하이브리드로 정착.
교훈: 모노 폰트는 표면적인 멋. 3분 가독성 테스트를 반드시 거쳐라. 짧은 mock 콘텐츠로 만족하지 말고 실제 길이의 한국어 본문으로 비교.
결정 1: 익명 닉네임 시스템
처음엔 Google OAuth 받은 그대로 댓글에 표시했다. CH LEE (Core) 같은 식으로 실명이 노출됐는데, 회계사 타겟에 이게 부담. 동종업계 감시 의식이 있어서 댓글 자체를 안 단다.
해결: Supabase trigger로 회원가입 시점에 자동 생성하는 익명 닉네임 시스템.
create or replace function public.handle_new_user()
returns trigger language plpgsql security definer
as $$
begin
insert into public.profiles (user_id, nickname)
values (new.id, '익명_' || substring(replace(new.id::text, '-', ''), 1, 6));
return new;
end;
$$;
create trigger on_auth_user_created
after insert on auth.users
for each row execute function public.handle_new_user();
comments 테이블에서 author_name/author_avatar 컬럼 제거하고, FK를 profiles(user_id)로 재연결. PostgREST가 자동으로 select('id, body, author:profiles(nickname, avatar_url)') 형태 join 지원. 결과: Google 실명 완전 숨김 + 닉네임 변경 UI 제공.
교훈: 한국에서 OAuth 로그인 = 신원 노출. 회계/세무처럼 직업적 익명성이 중요한 타겟엔 익명 레이어가 거의 필수.
결정 2: 디자인 시안은 한 페이지에
디자인 방향을 14가지 후보로 좁힌 뒤, 어느 게 좋은지 글로만 설명하면 답이 안 나왔다. 결국 한 HTML 페이지에 10개 시안을 모두 같은 mock 콘텐츠로 렌더해서 띄웠다.
- 좌측 sticky nav로 시안 1~10 점프
- 각 시안 = hero + 도구 카드 3개 + 뱃지 + 블로그 + 버튼
- 동일 콘텐츠 → 순수 디자인 차이만 비교
선택은 Mono Ledger (회계장부 + 테크 모노). 결정 시간 < 5분.
교훈: 디자인 결정은 같은 콘텐츠로 옆에 놓고 보는 것이 글 100줄보다 빠르다.
좋았던 것: MCP/CLI 우선 제안
처음엔 Vercel/Supabase 설정을 GUI 대시보드 클릭으로 안내했다. 유저가 한참 클릭한 뒤에야 “그거 CLI/MCP로도 되네?”라는 걸 깨달음. 그 뒤로는 외부 서비스 설정은 무조건 CLI/API/MCP 먼저 제안, GUI는 대안으로만.
Supabase MCP + Vercel MCP 연결한 뒤로는 거의 모든 작업을 채팅창에서 처리. SQL 마이그레이션도 supabase/migrations/*.sql 파일로 버전 관리하고 MCP로 직접 적용.
교훈: 외부 서비스의 자동화 가능성을 먼저 확인. 클릭 노가다는 시간이 비싼 협업의 적.
다음 할 일
- 실제 콘텐츠 축적 (이 글이 시작)
- Obsidian vault → 사이트 자동 싱크 파이프라인 (GitHub Actions)
- 자체 도구 페이지에 실제 다운로드 파일 연결
3일치 회고는 여기까지.
댓글을 불러오는 중...