Replit Agent 실전 사례: 솔로 파운더가 48시간 만에 SaaS 인보이스 생성기를 런칭한 방법
48시간 만에 SaaS 제품 런칭: Replit Agent 실전 케이스 스터디
프리랜서 개발자에게 3개월 일정과 1,500만 원의 견적을 받은 솔로 파운더가 Replit Agent를 활용해 48시간 만에 Stripe 결제, PDF 내보내기, PostgreSQL 멀티테넌트 아키텍처를 갖춘 SaaS 인보이스 생성기를 런칭한 실전 사례를 단계별로 분석합니다.
프로젝트 개요
| 항목 | 프리랜서 외주 | Replit Agent 활용 |
|---|---|---|
| 개발 기간 | 약 3개월 | 48시간 |
| 예상 비용 | 1,500만 원+ | Replit Core 월 $25 |
| 기술 스택 결정 | 별도 협의 필요 | AI 자동 제안 |
| 배포 | 별도 DevOps 필요 | 원클릭 배포 |
| 유지보수 | 추가 계약 필요 | 직접 수정 가능 |
# Replit Agent 프롬프트 예시
"Build a multi-tenant SaaS invoice generator with:
- Next.js 14 App Router frontend
- PostgreSQL with tenant isolation using Row-Level Security
- Stripe subscription billing (Basic/Pro/Enterprise)
- PDF invoice export with @react-pdf/renderer
- Authentication with NextAuth.js
- REST API with rate limiting per tenant"Agent는 이 프롬프트를 분석하여 디렉토리 구조, 패키지 의존성, 데이터베이스 스키마를 자동 생성합니다.
자동 생성된 PostgreSQL 멀티테넌트 스키마
— Replit Agent가 생성한 멀티테넌트 스키마
CREATE TABLE tenants (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
name VARCHAR(255) NOT NULL,
subdomain VARCHAR(63) UNIQUE NOT NULL,
stripe_customer_id VARCHAR(255),
plan VARCHAR(50) DEFAULT ‘basic’,
created_at TIMESTAMP DEFAULT NOW()
);
CREATE TABLE invoices (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id UUID REFERENCES tenants(id) ON DELETE CASCADE,
invoice_number VARCHAR(50) NOT NULL,
client_name VARCHAR(255) NOT NULL,
client_email VARCHAR(255),
items JSONB NOT NULL DEFAULT ’[]’,
subtotal DECIMAL(12,2) NOT NULL,
tax_rate DECIMAL(5,2) DEFAULT 0,
total DECIMAL(12,2) NOT NULL,
status VARCHAR(20) DEFAULT ‘draft’,
due_date DATE,
created_at TIMESTAMP DEFAULT NOW()
);
— Row-Level Security 활성화
ALTER TABLE invoices ENABLE ROW LEVEL SECURITY;
CREATE POLICY tenant_isolation ON invoices
USING (tenant_id = current_setting(‘app.current_tenant’)::UUID);
Step 2: Stripe 결제 통합 (2~8시간)
Agent에게 "Add Stripe subscription billing with three tiers"라고 요청하면 결제 로직이 자동 구현됩니다.
// lib/stripe.ts - Replit Agent 생성 코드
import Stripe from 'stripe';
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, {
apiVersion: ‘2024-12-18.acacia’,
});
export async function createCheckoutSession(
tenantId: string,
priceId: string
) {
const tenant = await db.query(
‘SELECT stripe_customer_id FROM tenants WHERE id = $1’,
[tenantId]
);
const session = await stripe.checkout.sessions.create({
customer: tenant.rows[0].stripe_customer_id,
payment_method_types: [‘card’],
line_items: [{ price: priceId, quantity: 1 }],
mode: ‘subscription’,
success_url: ${process.env.NEXT_PUBLIC_URL}/dashboard?success=true,
cancel_url: ${process.env.NEXT_PUBLIC_URL}/pricing?canceled=true,
metadata: { tenantId },
});
return session;
}
// Webhook 처리
export async function handleWebhook(event: Stripe.Event) {
switch (event.type) {
case ‘checkout.session.completed’: {
const session = event.data.object as Stripe.Checkout.Session;
await db.query(
‘UPDATE tenants SET plan = $1 WHERE id = $2’,
[session.metadata?.plan || ‘pro’, session.metadata?.tenantId]
);
break;
}
case ‘invoice.payment_failed’: {
// 결제 실패 시 알림 전송
break;
}
}
}
환경 변수 설정
Replit Secrets 탭에서 아래 값을 등록합니다:
STRIPE_SECRET_KEY=sk_test_YOUR_API_KEY
STRIPE_PUBLISHABLE_KEY=pk_test_YOUR_API_KEY
STRIPE_WEBHOOK_SECRET=whsec_YOUR_WEBHOOK_SECRET
DATABASE_URL=postgresql://user:password@host:5432/invoicedb
NEXTAUTH_SECRET=your-random-secret-string
## Step 3: PDF 인보이스 내보내기 (8~16시간)
// components/InvoicePDF.tsx import { Document, Page, Text, View, StyleSheet } from '@react-pdf/renderer';const styles = StyleSheet.create({ page: { padding: 40, fontSize: 11 }, header: { flexDirection: ‘row’, justifyContent: ‘space-between’, marginBottom: 30 }, table: { width: ‘100%’, marginTop: 20 }, tableRow: { flexDirection: ‘row’, borderBottomWidth: 1, borderColor: ‘#e5e7eb’, padding: 8 }, total: { marginTop: 20, textAlign: ‘right’, fontSize: 14, fontWeight: ‘bold’ }, });
export function InvoicePDF({ invoice }: { invoice: Invoice }) { return (); } <Text style={{ fontSize: 20, fontWeight: ‘bold’ }}> 인보이스 #{invoice.invoice_number} 발행일: {new Date(invoice.created_at).toLocaleDateString(‘ko-KR’)} 수신: {invoice.client_name} {invoice.client_email} {invoice.items.map((item: any, i: number) => ( <Text style={{ flex: 3 }}>{item.description} <Text style={{ flex: 1 }}>{item.quantity} <Text style={{ flex: 1 }}>₩{item.price.toLocaleString()} ))}합계: ₩{invoice.total.toLocaleString()}
Step 4: 배포 및 런칭 (16~48시간)
Replit의 Deploy 버튼을 클릭하면 자동으로 프로덕션 환경이 구성됩니다. Agent에게 추가 요청:
# Agent에게 최적화 요청
"Add rate limiting (100 requests/min per tenant),
error monitoring with Sentry,
and automated daily database backups"
Stripe CLI로 웹훅을 로컬 테스트합니다:
stripe listen --forward-to localhost:3000/api/webhooks/stripe
stripe trigger invoice.payment_succeeded
## Pro Tips: 파워 유저를 위한 팁
- **반복 프롬프트 활용:** "Refactor the invoice API to use server actions" 같은 점진적 요청으로 코드 품질을 높입니다.- **Agent 디버깅 모드:** 오류 발생 시 콘솔 로그를 복사해 Agent에게 붙여넣으면 자동으로 수정 코드를 제안합니다.- **.replit 파일 커스터마이징:** run = "npm run dev"와 같이 실행 명령을 직접 지정하면 Agent가 환경을 더 정확히 이해합니다.- **멀티테넌트 테스트:** Replit의 여러 브라우저 탭에서 서로 다른 테넌트로 로그인하여 데이터 격리를 검증하세요.- **Stripe 테스트 카드:** 4242 4242 4242 4242로 결제 플로우를 반복 테스트합니다.
## Troubleshooting: 자주 발생하는 문제 해결
- **PostgreSQL 연결 오류 (ECONNREFUSED):** Replit Secrets에서 DATABASE_URL이 올바르게 설정되었는지 확인하세요. Replit PostgreSQL 애드온을 사용하면 자동으로 연결 문자열이 제공됩니다.- **Stripe Webhook 서명 검증 실패:** STRIPE_WEBHOOK_SECRET이 whsec_로 시작하는지 확인하고, raw body parser가 webhook 라우트에 적용되어 있는지 점검하세요. export const config = { api: { bodyParser: false } };- **PDF 한글 폰트 깨짐:** @react-pdf/renderer에서 한글을 지원하려면 Noto Sans KR 폰트를 Font.register()로 등록해야 합니다.- **RLS 정책으로 인한 빈 결과:** SET app.current_tenant = 'tenant-uuid';가 각 쿼리 전에 실행되는지 미들웨어를 확인하세요.- **배포 후 504 타임아웃:** Replit 무료 플랜의 리소스 제한일 수 있습니다. Core 플랜으로 업그레이드하거나 API 응답에 캐싱을 추가하세요.
## 결과 요약
이 케이스 스터디에서 솔로 파운더는 Replit Agent를 통해 프리랜서 외주 대비 **개발 비용 99% 절감**(1,500만 원 → 월 $25), **개발 기간 98% 단축**(3개월 → 48시간)을 달성했습니다. 핵심은 Agent에게 명확하고 구체적인 프롬프트를 제공하고, 생성된 코드를 단계별로 검증하며, 점진적으로 기능을 확장한 워크플로우에 있습니다. ## 자주 묻는 질문 (FAQ)
Q1: Replit Agent가 생성한 코드를 프로덕션에 바로 사용해도 안전한가요?
Agent가 생성한 코드는 기능적으로 동작하지만, 보안 감사와 부하 테스트를 반드시 수행해야 합니다. 특히 인증, 결제, 데이터 격리 관련 코드는 수동으로 검증하세요. SQL 인젝션 방지를 위해 파라미터화된 쿼리가 사용되었는지, Stripe 웹훅 서명 검증이 올바른지 확인하는 것이 중요합니다.
Q2: 멀티테넌트 아키텍처에서 데이터 유출 위험은 없나요?
PostgreSQL Row-Level Security(RLS)를 사용하면 데이터베이스 수준에서 테넌트 간 데이터 격리가 보장됩니다. 다만 모든 API 엔드포인트에서 app.current_tenant 설정이 누락되지 않았는지 통합 테스트로 검증해야 하며, 관리자 쿼리 시 RLS를 우회하는 BYPASSRLS 권한은 최소한으로 제한해야 합니다.
Q3: 48시간 이후 기능 추가나 유지보수는 어떻게 하나요?
Replit Agent에게 “Add recurring invoice scheduling” 같은 자연어 요청으로 지속적으로 기능을 확장할 수 있습니다. 코드베이스가 이미 Agent의 맥락 안에 있으므로 기존 코드를 이해한 상태에서 일관된 패턴으로 기능을 추가합니다. Git 연동을 통해 버전 관리를 하면 롤백도 간편합니다.