콘텐츠로 이동

사용자에게 전달하기

이 장은 본인 컴퓨터에서 만든 SaaS 를 다른 사람의 브라우저에서 동작하게 옮기는 이야기입니다. 한두 명에게 베타로 보여 주는 정도부터, 결제까지 받는 production 까지 범위가 다양합니다. 처음부터 어디까지 갈지 정해 두고, 본인에게 맞는 절을 따라가세요.

시작 전에 한 가지 짚어 둡니다. base 가 자동으로 해 주는 것은 빌드(pnpm build)와 Edge Function 배포(supabase functions deploy)까지입니다. 그 산출물을 어느 호스팅에 올릴지, custom domain 을 어떻게 잡을지, Stripe production webhook endpoint 를 어디로 박을지는 본인이 직접 결정하고 직접 누르는 자리입니다. 도구가 그 자리까지 끼어들면 본인의 production 키나 결제 계정을 다루게 되어 안전하지 않기 때문입니다.

단계누가어떻게
Vite SPA 빌드base 자동pnpm build 또는 deploy-saas 스킬
Edge Function 배포base 자동supabase functions deploy <name>
호스팅에 올리기본인Vercel, Netlify, Cloudflare Pages, self-host 중 하나
Stripe production webhook 등록본인Stripe 대시보드에서 endpoint URL 박기
Supabase production 프로젝트 분리본인Supabase 대시보드에서 새 프로젝트 + 키 분리

아래 네 절 중 본인 상황에 맞는 하나만 골라 그 단계대로 따라가시면 됩니다.

경로어울리는 자리미리 갖춰야 할 것git push 자동 deploy
Vercel가장 손이 덜 가는 자리Vercel 계정있음
NetlifyVercel 대안Netlify 계정있음
Cloudflare PagesCloudflare 인프라 통합Cloudflare 계정있음
self-host사내 정책상 외부 호스팅 불가정적 호스팅 가능한 서버수동 또는 CI

처음이라면 Vercel 이 거의 항상 충분합니다. git push 한 번으로 preview deploy 가 떨어지고, main 브랜치 push 가 production 으로 갑니다. custom domain 과 SSL 도 자동입니다.

deploy-saas 스킬이 끝나면 docs/deployment/DEPLOYMENT.md 파일이 새로 생깁니다. 이 장은 그 파일의 긴 호흡 동반자입니다.

0) Supabase production 프로젝트 분리

섹션 제목: “0) Supabase production 프로젝트 분리”

어느 호스팅을 고르든 먼저 결정할 자리입니다. 지금까지 작업한 Supabase 프로젝트는 staging 자리에 두고, production 용으로 새 프로젝트를 하나 더 만듭니다.

Supabase 조직
├── my-saas-production ← 실 사용자, 실 Stripe live key
└── my-saas-staging ← 개발자 테스트, Stripe test key

같은 프로젝트에서 RLS 정책 한 줄 잘못 박힌 실수가 실사용자 데이터에 닿는 자리를 막아 주는 분리입니다. 각 프로젝트가 자기 SUPABASE_URL, ANON_KEY, SERVICE_ROLE_KEY 를 들고 있고, 호스팅의 production env 와 preview env 가 다른 프로젝트를 가리키게 잡습니다.

마이그레이션은 같은 SQL 을 양쪽에 적용합니다.

Terminal window
supabase link --project-ref staging-ref
supabase db push # staging 에 먼저
# 확인 후
supabase link --project-ref production-ref
supabase db push # production 에

같은 마이그레이션이 두 환경에 같은 모양으로 들어가야 drift 가 새지 않습니다.

가장 단순한 길이고, 보통 시작점으로 정답에 가깝습니다.

Terminal window
npm i -g vercel # CLI 한 번만 깔면 됨
vercel link # 프로젝트 연결
vercel env pull .env.local # 원격 env 를 로컬로 (선택)
vercel deploy --prod # production deploy

vercel link 가 한 번 잡힌 다음에는 git push 로 preview deploy 가 자동으로 떨어집니다. production deploy 는 main 브랜치 push 또는 vercel deploy --prod 로 명시 호출합니다.

env 주입은 대시보드의 Settings → Environment Variables 자리. CLI 로도 됩니다.

Terminal window
vercel env add VITE_SUPABASE_URL production
vercel env add VITE_SUPABASE_ANON_KEY production
vercel env add VITE_STRIPE_PUBLISHABLE_KEY production
vercel env add VITE_API_MODE production # edge-functions

SUPABASE_SERVICE_ROLE_KEYSTRIPE_SECRET_KEY 는 여기에 박지 않습니다. 그 둘은 Edge Function 쪽에 supabase secrets set 으로 박힙니다. 호스팅 env 에 박으면 SPA 번들에 새지는 않더라도 build 시점에 노출 위험이 생깁니다.

custom domain 은 대시보드의 Domains 자리. DNS A 또는 CNAME 레코드 한 줄 박는 자리고, Vercel 이 SSL 인증서를 자동 발급합니다.

Terminal window
npm i -g netlify-cli
netlify link
netlify env:import .env.production
netlify deploy --prod

netlify env:import.env.production 파일을 통째로 흡수합니다. .env.production 은 production 키만 들어있는 파일이고 .gitignore 에 있어야 합니다. import 후 그 파일은 지우는 편이 안전합니다.

build 명령은 netlify.toml 에 박힙니다.

[build]
command = "pnpm build"
publish = "dist"

custom domain 과 SSL 은 Vercel 과 동일한 흐름입니다.

Terminal window
npm i -g wrangler
wrangler login
wrangler pages deploy dist --project-name=my-saas

Cloudflare Pages 는 git push 로 자동 deploy 를 트리거하거나 wrangler pages deploy dist 로 수동 push 합니다. 두 갈래 다 잘 동작합니다.

env 주입은 대시보드 또는 wrangler.

Terminal window
wrangler pages secret put VITE_SUPABASE_URL --project-name=my-saas
wrangler pages secret put VITE_SUPABASE_ANON_KEY --project-name=my-saas
wrangler pages secret put VITE_STRIPE_PUBLISHABLE_KEY --project-name=my-saas

custom domain 은 대시보드의 Custom domains 자리. Cloudflare 가 DNS 도 같이 관리하면 한 자리에서 끝나서 편합니다.

회사 정책상 외부 호스팅이 불가하거나, 자체 인프라 위에 올리고 싶을 때입니다.

Terminal window
pnpm build
# dist/ 를 본인 서버의 nginx 또는 s3 static hosting 자리로 옮김

dist/ 는 정적 파일 묶음입니다. index.html, vendor chunk, route 별 chunk, CSS 번들. SPA 라 entry point 가 하나여서 nginx 의 try_files $uri /index.html 설정만 박혀 있으면 됩니다.

env 변수는 build 시점에 번들에 박혀 있어서, 환경별로 다른 변수를 쓰려면 환경마다 따로 빌드합니다. 또는 build 시점에 환경변수를 외부에서 주입하는 CI 파이프라인을 짭니다.

호스팅에 올라간 자리에서 webhook 이 동작하려면 Stripe 대시보드에서 endpoint 를 새로 등록해야 합니다. 로컬 개발의 stripe listen 과는 다른 자리입니다.

  1. Stripe 대시보드 좌측 메뉴의 Developers → Webhooks 로 갑니다.
  2. 우측 상단의 Test mode 토글이 production 으로 가 있는지 확인합니다 (production 배포라면). test mode 에서 등록한 endpoint 는 production 결제 이벤트를 못 받습니다.
  3. Add endpoint 를 누르고 endpoint URL 을 박습니다.
  4. URL 은 Edge Function 의 public URL 입니다. https://<project-ref>.supabase.co/functions/v1/stripe-webhook 형태.
  5. Listen to 자리에서 본인 SaaS 가 받을 이벤트를 고릅니다. 보통 checkout.session.completed, customer.subscription.updated, customer.subscription.deleted, invoice.paid 정도.
  6. 등록이 끝나면 endpoint 의 Signing secret (whsec_...) 을 복사합니다.
  7. 그 secret 을 production Supabase 프로젝트에 박습니다.
Terminal window
supabase link --project-ref production-ref
supabase secrets set STRIPE_WEBHOOK_SECRET=whsec_...

webhook 이 정상으로 등록됐는지는 Stripe 대시보드의 endpoint 페이지에서 Send test webhook 을 눌러 확인합니다. Edge Function 로그에 검증 통과 줄이 떨어지면 정상입니다.

SaaS 가 외부 서비스에 닿는 자리(Supabase, Stripe, 분석 도구 등)는 Content Security Policy 의 connect-src 에 명시되어야 합니다. base 의 index.html 에 기본 골격이 박혀 있습니다.

<meta http-equiv="Content-Security-Policy" content="
default-src 'self';
connect-src 'self' https://*.supabase.co https://api.stripe.com;
script-src 'self' https://js.stripe.com;
">

본인 SaaS 가 추가로 닿는 외부 도메인이 있다면 (예: 분석 도구, CDN) 그 자리에 붙입니다. 이 자리는 fork 시 채워집니다.

production 환경에서 잘못되면 거의 회복 불가에 가까운 자리들입니다. 첫날부터 1순위로 다루세요.

  • STRIPE_SECRET_KEY (sk_live_...) — Stripe 대시보드에서 회전 가능. 노출되면 즉시 회전
  • SUPABASE_SERVICE_ROLE_KEY — Supabase 대시보드에서 회전 가능. 회전하면 모든 클라이언트가 끊김
  • 위 두 자리는 supabase secrets set 으로만 박힙니다. .env* 파일에 들어가면 안 됩니다.
  • STRIPE_WEBHOOK_SECRET 은 endpoint 별로 다릅니다. test mode 와 production 자리가 다른 값입니다.

.env* 파일을 git 에 커밋하지 마세요. .env.example 만 git 에 들어갑니다. 시작 전에 .gitignore.env* 가 들어있는지 확인하세요.

이 base 는 goldtagworks 로부터 라이선스를 받은 상업 템플릿입니다. 그 위에서 본인이 만든 SaaS 는 본인이 정한 라이선스 조건으로 자유롭게 빌드하고 판매할 수 있습니다. 본인 사용자는 goldtagworks 와는 아무 관계도 가지지 않습니다. 사용자에게 보이는 것은 본인의 SaaS 뿐입니다.

다만 base 자체의 사용에 두 가지 제약이 있습니다.

  • saas-base 의 소스 코드나 이 매뉴얼을 누군가에게 재배포하지 마세요. base 위에서 만든 본인의 SaaS 만 배포합니다.
  • 본인 작업 사본에는 LICENSECOMMERCIAL-LICENSE.md 를 그대로 둡니다. 빌드 산출물 dist/ 안에까지 포함시킬 필요는 없습니다. 사용자에게 가는 것은 본인 SaaS 이지 base 가 아닙니다.

본인 SaaS 의 배포 라이선스는 본인이 정합니다. SaaS 운영에서는 보통 약관(Terms of Service) 과 개인정보 처리방침(Privacy Policy) 두 자리가 더 큽니다. 결제를 받기 전까지는 이 두 자리도 마무리되어야 합니다. base 가 들고 있는 LICENSECOMMERCIAL-LICENSE.md 는 base 의 사용 조건이지 본인 SaaS 의 약관이 아닙니다.

여기까지가 base 가 가정하는 모든 배포 경로입니다. 흐름이 한 번 익숙해졌다면 LICENSEpackage.json 이 본인 SaaS 정보로 바뀌어 있는지, .env* 파일이 git 에 안 들어갔는지 한 번 더 확인합니다.

배포 도중 막히는 자리가 있다면 09-troubleshooting.md 에서 시작하고, 자동 생성된 docs/deployment/DEPLOYMENT.md 도 옆에 두고 같이 보세요.