Merge: SaaS 전환 마이그레이션 P1·P2·P4 + P3 골격
- 블로그 자동화 완전 제거 - SaaS 제품 카탈로그(/packages) + 네비 3축(SaaS·음악·외주) 재편 - 음악을 'AI 음악 생성 개발 가이드 패키지' 단품으로 디벨롭 - 계획 문서 진행 상태 반영 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
12
STRATEGY.md
12
STRATEGY.md
@@ -1,8 +1,18 @@
|
|||||||
# 쟁승메이드 사업 전략 플레이북
|
# 쟁승메이드 사업 전략 플레이북
|
||||||
|
|
||||||
> 최초 작성: 2026-03-24 | 마지막 업데이트: 2026-03-24
|
> 최초 작성: 2026-03-24 | 마지막 업데이트: 2026-05-31
|
||||||
> 작성 방식: 마케터 · 인플루언서 · 사업가 3인 원탁 회의 기반
|
> 작성 방식: 마케터 · 인플루언서 · 사업가 3인 원탁 회의 기반
|
||||||
|
|
||||||
|
> **⚠️ 정체성 재정의 (2026-05-29, 본 문서 일부 전제 갱신)**
|
||||||
|
> 현재 정체성은 **"SaaS 제품 판매(메인) + 커스텀 외주(보조) 병행"**이다.
|
||||||
|
> - **외주 유입 채널: 크몽·숨고 등 외부 프리랜서 마켓은 사용하지 않는다.**
|
||||||
|
> 대신 **인스타 카드뉴스(Hedgy75) 직접 유입**으로 전환한다.
|
||||||
|
> → 아래 "크몽/숨고 AI 자동화 세팅 대행" 등 마켓 전제 섹션은 과거 전략 기록이며,
|
||||||
|
> 현 방침과 충돌 시 본 정책이 우선한다.
|
||||||
|
> - SaaS 제품 카탈로그는 `/packages`, AI 음악은 단품 가이드 패키지(`/music`)로 분리.
|
||||||
|
> - 블로그 자동화는 폐기(2026-05-17 결정, 코드 제거 완료).
|
||||||
|
> 상세: `docs/superpowers/plans/2026-05-31-saas-pivot-migration.md`
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 📊 현황 진단 — 3인 전문가 평가
|
## 📊 현황 진단 — 3인 전문가 평가
|
||||||
|
|||||||
@@ -75,22 +75,28 @@ export default function PublicShell({ children }: { children: React.ReactNode })
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* 우 — Link groups */}
|
{/* 우 — Link groups */}
|
||||||
<div className="grid grid-cols-2 sm:grid-cols-3 gap-10">
|
<div className="grid grid-cols-2 sm:grid-cols-4 gap-10">
|
||||||
<div>
|
<div>
|
||||||
<p className="font-mono text-[11px] tracking-widest uppercase text-white/40 mb-4">Music</p>
|
<p className="font-mono text-[11px] tracking-widest uppercase text-white/40 mb-4">SaaS 제품</p>
|
||||||
<ul className="space-y-2.5">
|
<ul className="space-y-2.5">
|
||||||
<li><Link href="/music/packs" className="hover:text-white transition">AI 음악 팩</Link></li>
|
<li><Link href="/packages" className="hover:text-white transition">제품 카탈로그</Link></li>
|
||||||
|
<li><Link href="/packages" className="hover:text-white transition">출시 알림 신청</Link></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<p className="font-mono text-[11px] tracking-widest uppercase text-white/40 mb-4">AI 음악</p>
|
||||||
|
<ul className="space-y-2.5">
|
||||||
|
<li><Link href="/music/packs" className="hover:text-white transition">음악 가이드 패키지</Link></li>
|
||||||
<li><Link href="/music/samples" className="hover:text-white transition">샘플 갤러리</Link></li>
|
<li><Link href="/music/samples" className="hover:text-white transition">샘플 갤러리</Link></li>
|
||||||
<li><Link href="/music/packs#pricing" className="hover:text-white transition">가격</Link></li>
|
<li><Link href="/music/packs#pricing" className="hover:text-white transition">가격</Link></li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<p className="font-mono text-[11px] tracking-widest uppercase text-white/40 mb-4">Custom Build</p>
|
<p className="font-mono text-[11px] tracking-widest uppercase text-white/40 mb-4">커스텀 외주</p>
|
||||||
<ul className="space-y-2.5">
|
<ul className="space-y-2.5">
|
||||||
<li><Link href="/work/freelance" className="hover:text-white transition">외주 개발</Link></li>
|
<li><Link href="/work/freelance" className="hover:text-white transition">외주 개발</Link></li>
|
||||||
<li><Link href="/work/website" className="hover:text-white transition">웹사이트 제작</Link></li>
|
<li><Link href="/work/website" className="hover:text-white transition">웹사이트 제작</Link></li>
|
||||||
<li><Link href="/work/saju" className="hover:text-white transition">AI 사주</Link></li>
|
<li><Link href="/work/saju" className="hover:text-white transition">AI 사주</Link></li>
|
||||||
<li><Link href="/work/blog" className="hover:text-white transition">블로그 자동화</Link></li>
|
|
||||||
<li><a href="mailto:bgg8988@gmail.com" className="hover:text-white transition">문의하기</a></li>
|
<li><a href="mailto:bgg8988@gmail.com" className="hover:text-white transition">문의하기</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -7,8 +7,9 @@ import { createClient } from '@/lib/supabase/client';
|
|||||||
import type { User } from '@supabase/supabase-js';
|
import type { User } from '@supabase/supabase-js';
|
||||||
|
|
||||||
const LINKS = [
|
const LINKS = [
|
||||||
{ href: '/music', label: 'Music' },
|
{ href: '/packages', label: 'SaaS 제품' },
|
||||||
{ href: '/work', label: 'Custom Build' },
|
{ href: '/music', label: 'AI 음악' },
|
||||||
|
{ href: '/work', label: '커스텀 외주' },
|
||||||
];
|
];
|
||||||
|
|
||||||
export default function TopNav() {
|
export default function TopNav() {
|
||||||
|
|||||||
@@ -27,7 +27,6 @@ export const metadata: Metadata = {
|
|||||||
"유튜브 쇼츠 음악",
|
"유튜브 쇼츠 음악",
|
||||||
"AI 뮤비",
|
"AI 뮤비",
|
||||||
"음악 프롬프트",
|
"음악 프롬프트",
|
||||||
"블로그 자동화",
|
|
||||||
"AI 사주",
|
"AI 사주",
|
||||||
],
|
],
|
||||||
authors: [{ name: "박재오", url: "https://jaengseung-made.com" }],
|
authors: [{ name: "박재오", url: "https://jaengseung-made.com" }],
|
||||||
@@ -74,14 +73,14 @@ const jsonLd = {
|
|||||||
email: 'bgg8988@gmail.com',
|
email: 'bgg8988@gmail.com',
|
||||||
telephone: '010-3907-1392',
|
telephone: '010-3907-1392',
|
||||||
knowsAbout: ['Python', 'Java', 'Spring Boot', 'Next.js', 'AI 프롬프트', 'AI 자동화', '업무 자동화', 'ChatGPT', 'Claude'],
|
knowsAbout: ['Python', 'Java', 'Spring Boot', 'Next.js', 'AI 프롬프트', 'AI 자동화', '업무 자동화', 'ChatGPT', 'Claude'],
|
||||||
description: '현직 엔지니어. AI 음악 구조 설계 팩, 블로그 자동화 팩, AI 사주 분석 등 AI 크리에이티브 도구를 직접 개발·운영합니다.',
|
description: '현직 엔지니어. AI 음악 생성 개발 가이드 패키지, AI 사주 분석 등 AI 크리에이티브 도구를 직접 개발·운영합니다.',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'@type': 'LocalBusiness',
|
'@type': 'LocalBusiness',
|
||||||
'@id': 'https://jaengseung-made.com/#business',
|
'@id': 'https://jaengseung-made.com/#business',
|
||||||
name: '쟁승메이드',
|
name: '쟁승메이드',
|
||||||
url: 'https://jaengseung-made.com',
|
url: 'https://jaengseung-made.com',
|
||||||
description: 'AI 음악 작곡·뮤비 구조 설계 팩, 블로그 자동화 팩, AI 사주 분석. 현직 엔지니어가 직접 설계·운영하는 AI 크리에이티브 스토어.',
|
description: 'AI 음악 생성 개발 가이드 패키지, AI 사주 분석. 현직 엔지니어가 직접 설계·운영하는 AI 크리에이티브 스토어.',
|
||||||
email: 'bgg8988@gmail.com',
|
email: 'bgg8988@gmail.com',
|
||||||
telephone: '010-3907-1392',
|
telephone: '010-3907-1392',
|
||||||
priceRange: '₩',
|
priceRange: '₩',
|
||||||
@@ -90,10 +89,9 @@ const jsonLd = {
|
|||||||
'@type': 'OfferCatalog',
|
'@type': 'OfferCatalog',
|
||||||
name: '쟁승메이드 AI 도구 · 서비스',
|
name: '쟁승메이드 AI 도구 · 서비스',
|
||||||
itemListElement: [
|
itemListElement: [
|
||||||
{ '@type': 'Offer', price: '39000', priceCurrency: 'KRW', availability: 'https://schema.org/InStock', url: 'https://jaengseung-made.com/music/packs', itemOffered: { '@type': 'Product', name: 'AI 음악 마스터 구조 팩 (입문)', url: 'https://jaengseung-made.com/music/packs', description: 'Suno 프롬프트 + MV 워크플로우 + 저작권 가이드 + 템플릿 PDF + 샘플 프로젝트. 4단계 AI 음악 제작 공정.' } },
|
{ '@type': 'Offer', price: '39000', priceCurrency: 'KRW', availability: 'https://schema.org/InStock', url: 'https://jaengseung-made.com/music/packs', itemOffered: { '@type': 'Product', name: 'AI 음악 생성 개발 가이드 패키지 (입문)', url: 'https://jaengseung-made.com/music/packs', description: 'Suno 프롬프트 조합법 + MV 워크플로우 + 저작권 가이드 + 템플릿 PDF + 샘플 프로젝트. AI 음악 생성 개발 가이드 (1회 결제).' } },
|
||||||
{ '@type': 'Offer', price: '99000', priceCurrency: 'KRW', availability: 'https://schema.org/InStock', url: 'https://jaengseung-made.com/music/packs', itemOffered: { '@type': 'Product', name: 'AI 음악 마스터 구조 팩 (프로)', url: 'https://jaengseung-made.com/music/packs', description: '입문 전체 + 샘플 프로젝트 1개(.prj · 영상 포함).' } },
|
{ '@type': 'Offer', price: '99000', priceCurrency: 'KRW', availability: 'https://schema.org/InStock', url: 'https://jaengseung-made.com/music/packs', itemOffered: { '@type': 'Product', name: 'AI 음악 생성 개발 가이드 패키지 (프로)', url: 'https://jaengseung-made.com/music/packs', description: '입문 전체 + 샘플 프로젝트 1개(.prj · 영상 포함). 1회 결제.' } },
|
||||||
{ '@type': 'Offer', price: '149000', priceCurrency: 'KRW', availability: 'https://schema.org/InStock', url: 'https://jaengseung-made.com/music/packs', itemOffered: { '@type': 'Product', name: 'AI 음악 마스터 구조 팩 (마스터)', url: 'https://jaengseung-made.com/music/packs', description: '프로 전체 + 샘플 다수 + 우선 업데이트·베타 선공개.' } },
|
{ '@type': 'Offer', price: '149000', priceCurrency: 'KRW', availability: 'https://schema.org/InStock', url: 'https://jaengseung-made.com/music/packs', itemOffered: { '@type': 'Product', name: 'AI 음악 생성 개발 가이드 패키지 (마스터)', url: 'https://jaengseung-made.com/music/packs', description: '프로 전체 + 샘플 다수 + 우선 업데이트·베타 선공개. 1회 결제.' } },
|
||||||
{ '@type': 'Offer', price: '29000', priceCurrency: 'KRW', availability: 'https://schema.org/InStock', url: 'https://jaengseung-made.com/work/blog', itemOffered: { '@type': 'Product', name: '블로그 자동화 솔루션 팩', url: 'https://jaengseung-made.com/work/blog', description: '쿠팡파트너스·애드포스트 수익화 프롬프트 조합법 + 구조 템플릿 PDF + 샘플.' } },
|
|
||||||
{ '@type': 'Offer', price: '0', priceCurrency: 'KRW', url: 'https://jaengseung-made.com/work/saju', itemOffered: { '@type': 'Service', name: 'AI 사주 분석', url: 'https://jaengseung-made.com/work/saju', description: '생년월일 기반 AI 사주팔자 분석. 무료 체험 가능.' } },
|
{ '@type': 'Offer', price: '0', priceCurrency: 'KRW', url: 'https://jaengseung-made.com/work/saju', itemOffered: { '@type': 'Service', name: 'AI 사주 분석', url: 'https://jaengseung-made.com/work/saju', description: '생년월일 기반 AI 사주팔자 분석. 무료 체험 가능.' } },
|
||||||
{
|
{
|
||||||
'@type': 'Offer',
|
'@type': 'Offer',
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ export default function RefundPage() {
|
|||||||
|
|
||||||
<section>
|
<section>
|
||||||
<h2 className="text-lg font-bold text-slate-800 mt-8 mb-3">1. 디지털 콘텐츠 (즉시 제공 상품)</h2>
|
<h2 className="text-lg font-bold text-slate-800 mt-8 mb-3">1. 디지털 콘텐츠 (즉시 제공 상품)</h2>
|
||||||
<p className="font-medium text-slate-700">대상: AI 음악 마스터 구조 팩, 블로그 자동화 솔루션 팩, AI 사주 리포트 등 디지털 콘텐츠 상품 일체</p>
|
<p className="font-medium text-slate-700">대상: AI 음악 생성 개발 가이드 패키지, AI 사주 리포트 등 디지털 콘텐츠 상품 일체</p>
|
||||||
<p className="text-xs text-slate-500 mt-2">
|
<p className="text-xs text-slate-500 mt-2">
|
||||||
전자상거래법 제17조 제2항 제5호에 따라, 디지털 콘텐츠는 제공이 개시된 이후 청약철회가 제한됩니다.
|
전자상거래법 제17조 제2항 제5호에 따라, 디지털 콘텐츠는 제공이 개시된 이후 청약철회가 제한됩니다.
|
||||||
회사는 구매 전 무료 샘플·미리보기를 제공하고, 구매 시 환불 제한 사항에 대한 소비자 동의를 확인합니다.
|
회사는 구매 전 무료 샘플·미리보기를 제공하고, 구매 시 환불 제한 사항에 대한 소비자 동의를 확인합니다.
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import type { Metadata } from 'next';
|
import type { Metadata } from 'next';
|
||||||
|
|
||||||
export const metadata: Metadata = {
|
export const metadata: Metadata = {
|
||||||
title: 'AI 음악 마스터 구조 팩 | Suno · MV · 유튜브 쇼츠',
|
title: 'AI 음악 생성 개발 가이드 패키지 | Suno · MV · 유튜브 쇼츠',
|
||||||
description:
|
description:
|
||||||
'엔지니어가 설계한 4단계 AI 음악 제작 공정. Suno 프롬프트 조합법 + MV 비디오 생성 워크플로우 + 저작권 가이드 + 템플릿 PDF + 샘플 프로젝트. 입문 ₩39k / 프로 ₩99k / 마스터 ₩149k.',
|
'엔지니어가 설계한 AI 음악 생성 개발 가이드. Suno 프롬프트 조합법 + MV 비디오 생성 워크플로우 + 저작권 가이드 + 템플릿 PDF + 샘플 프로젝트. 1회 결제 · 입문 ₩39k / 프로 ₩99k / 마스터 ₩149k.',
|
||||||
keywords: [
|
keywords: [
|
||||||
'AI 음악 만들기',
|
'AI 음악 만들기',
|
||||||
'Suno 프롬프트',
|
'Suno 프롬프트',
|
||||||
@@ -16,9 +16,9 @@ export const metadata: Metadata = {
|
|||||||
'Runway AI 비디오',
|
'Runway AI 비디오',
|
||||||
],
|
],
|
||||||
openGraph: {
|
openGraph: {
|
||||||
title: 'AI 음악 마스터 구조 팩 | 쟁승메이드',
|
title: 'AI 음악 생성 개발 가이드 패키지 | 쟁승메이드',
|
||||||
description:
|
description:
|
||||||
'네 사연을 노래로. 쇼츠까지 한 번에. 4단계 AI 음악 공정 · Suno Pro 검증 · 평생 업데이트.',
|
'네 사연을 노래로. 쇼츠까지 한 번에. AI 음악 생성 개발 가이드 · Suno Pro 검증 · 평생 업데이트.',
|
||||||
url: 'https://jaengseung-made.com/music/packs',
|
url: 'https://jaengseung-made.com/music/packs',
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ const TIERS: Record<Tier, { name: string; price: string; priceNum: string; desc:
|
|||||||
name: '입문',
|
name: '입문',
|
||||||
price: '₩39,000',
|
price: '₩39,000',
|
||||||
priceNum: '39,000',
|
priceNum: '39,000',
|
||||||
desc: '첫 AI 음악을 위한 필수 구성',
|
desc: 'AI 음악 생성을 처음 시작하는 개발 가이드',
|
||||||
features: [
|
features: [
|
||||||
'Suno 프롬프트 조합법 20종',
|
'Suno 프롬프트 조합법 20종',
|
||||||
'구조 템플릿 PDF 40p',
|
'구조 템플릿 PDF 40p',
|
||||||
@@ -25,7 +25,7 @@ const TIERS: Record<Tier, { name: string; price: string; priceNum: string; desc:
|
|||||||
name: '프로',
|
name: '프로',
|
||||||
price: '₩99,000',
|
price: '₩99,000',
|
||||||
priceNum: '99,000',
|
priceNum: '99,000',
|
||||||
desc: '쇼츠 업로드까지 완성하는 풀세트',
|
desc: '쇼츠 업로드까지 반복 가능한 워크플로우 가이드',
|
||||||
highlight: true,
|
highlight: true,
|
||||||
features: [
|
features: [
|
||||||
'입문 전체 포함',
|
'입문 전체 포함',
|
||||||
@@ -38,7 +38,7 @@ const TIERS: Record<Tier, { name: string; price: string; priceNum: string; desc:
|
|||||||
name: '마스터',
|
name: '마스터',
|
||||||
price: '₩149,000',
|
price: '₩149,000',
|
||||||
priceNum: '149,000',
|
priceNum: '149,000',
|
||||||
desc: '여러 장르·포맷을 커버하는 마스터피스',
|
desc: '여러 장르·포맷을 커버하는 마스터 가이드',
|
||||||
features: [
|
features: [
|
||||||
'프로 전체 포함',
|
'프로 전체 포함',
|
||||||
'샘플 프로젝트 장르별 3종',
|
'샘플 프로젝트 장르별 3종',
|
||||||
@@ -292,7 +292,7 @@ export default function MusicServicePage() {
|
|||||||
<PurchaseAgreementModal
|
<PurchaseAgreementModal
|
||||||
isOpen={!!selectedTier}
|
isOpen={!!selectedTier}
|
||||||
onClose={() => setSelectedTier(null)}
|
onClose={() => setSelectedTier(null)}
|
||||||
productName={`AI 음악 마스터 팩 · ${TIERS[selectedTier].name}`}
|
productName={`AI 음악 생성 개발 가이드 · ${TIERS[selectedTier].name}`}
|
||||||
price={TIERS[selectedTier].price}
|
price={TIERS[selectedTier].price}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|||||||
18
app/packages/layout.tsx
Normal file
18
app/packages/layout.tsx
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
import type { Metadata } from 'next';
|
||||||
|
|
||||||
|
export const metadata: Metadata = {
|
||||||
|
title: 'SaaS 제품 · 월 구독 패키지',
|
||||||
|
description:
|
||||||
|
'현직 엔지니어가 실제 운영하며 검증한 자동화를 월 구독 SaaS 제품으로 제공합니다. 첫 제품 준비 중 — 출시 알림을 신청하세요.',
|
||||||
|
keywords: ['SaaS', '자동화 구독', '월 구독 자동화', 'AI 자동화 제품', '쟁승메이드'],
|
||||||
|
openGraph: {
|
||||||
|
title: 'SaaS 제품 · 월 구독 패키지 | 쟁승메이드',
|
||||||
|
description:
|
||||||
|
'검증된 자동화를 SaaS로. 현직 엔지니어가 직접 운영·검증한 자동화 제품 카탈로그.',
|
||||||
|
url: 'https://jaengseung-made.com/packages',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function PackagesLayout({ children }: { children: React.ReactNode }) {
|
||||||
|
return children;
|
||||||
|
}
|
||||||
173
app/packages/page.tsx
Normal file
173
app/packages/page.tsx
Normal file
@@ -0,0 +1,173 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import { useState } from 'react';
|
||||||
|
import Link from 'next/link';
|
||||||
|
import ContactModal from '@/app/components/ContactModal';
|
||||||
|
import { trackCTAClick } from '@/lib/gtag';
|
||||||
|
import {
|
||||||
|
getAvailablePackages,
|
||||||
|
getComingSoonPackages,
|
||||||
|
type SaasCatalogItem,
|
||||||
|
} from '@/lib/saas-catalog';
|
||||||
|
|
||||||
|
const WAITLIST_SERVICE = 'SaaS 출시 알림 신청';
|
||||||
|
|
||||||
|
function PackageCard({ pkg, dimmed }: { pkg: SaasCatalogItem; dimmed?: boolean }) {
|
||||||
|
const inner = (
|
||||||
|
<>
|
||||||
|
<div className="flex items-center justify-between mb-3">
|
||||||
|
<p className="font-mono text-[10px] uppercase tracking-widest text-white/50">
|
||||||
|
{pkg.category}
|
||||||
|
</p>
|
||||||
|
{pkg.badge && (
|
||||||
|
<span className="text-[10px] font-bold uppercase tracking-wider px-2 py-0.5 rounded-full border border-white/30 text-white/80">
|
||||||
|
{pkg.badge}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
{dimmed && !pkg.badge && (
|
||||||
|
<span className="text-[10px] font-bold uppercase tracking-wider px-2 py-0.5 rounded-full border border-white/20 text-white/50">
|
||||||
|
Coming Soon
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<h3 className="kx-display text-xl font-bold text-white mb-1.5">{pkg.name}</h3>
|
||||||
|
<p className="text-sm text-white/70 mb-3">{pkg.tagline}</p>
|
||||||
|
<p className="text-xs text-white/55 leading-relaxed mb-4 flex-1">{pkg.description}</p>
|
||||||
|
<ul className="space-y-2 mb-5">
|
||||||
|
{pkg.features.map((f) => (
|
||||||
|
<li key={f} className="flex gap-2 text-xs text-white/70">
|
||||||
|
<span className="text-white/40">·</span>
|
||||||
|
<span className="leading-relaxed">{f}</span>
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
<div className="mt-auto flex items-center justify-between">
|
||||||
|
{pkg.priceLabel ? (
|
||||||
|
<span className="font-mono text-sm text-white">{pkg.priceLabel}</span>
|
||||||
|
) : (
|
||||||
|
<span className="font-mono text-xs text-white/40">가격 준비 중</span>
|
||||||
|
)}
|
||||||
|
{!dimmed && <span aria-hidden className="text-white/50 text-sm">→</span>}
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
|
||||||
|
const base =
|
||||||
|
'group rounded-2xl border p-6 flex flex-col transition';
|
||||||
|
if (dimmed) {
|
||||||
|
return (
|
||||||
|
<div className={`${base} border-white/10 bg-white/[0.01] opacity-60`}>{inner}</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<Link
|
||||||
|
href={pkg.href ?? '#'}
|
||||||
|
onClick={() => trackCTAClick(`packages_card_${pkg.slug}`)}
|
||||||
|
className={`${base} border-white/15 bg-white/[0.02] hover:border-white/40 hover:bg-white/[0.05]`}
|
||||||
|
style={{ textDecoration: 'none' }}
|
||||||
|
>
|
||||||
|
{inner}
|
||||||
|
</Link>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function PackagesPage() {
|
||||||
|
const [modalOpen, setModalOpen] = useState(false);
|
||||||
|
const available = getAvailablePackages();
|
||||||
|
const comingSoon = getComingSoonPackages();
|
||||||
|
const isEmpty = available.length === 0;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="min-h-screen bg-black text-white">
|
||||||
|
<ContactModal
|
||||||
|
isOpen={modalOpen}
|
||||||
|
onClose={() => setModalOpen(false)}
|
||||||
|
service={WAITLIST_SERVICE}
|
||||||
|
checklist={['관심 있는 업무·자동화 분야', '연락받을 이메일', '현재 겪는 반복 업무(선택)']}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* Hero */}
|
||||||
|
<section className="relative w-full min-h-[60vh] flex items-center justify-center px-6 border-b border-white/10">
|
||||||
|
<div className="absolute inset-0 bg-gradient-to-b from-[#0a0618] to-black pointer-events-none" />
|
||||||
|
<div className="relative z-10 max-w-3xl mx-auto text-center">
|
||||||
|
<p className="font-mono text-[11px] tracking-widest uppercase text-white/50 mb-4">
|
||||||
|
SaaS Products
|
||||||
|
</p>
|
||||||
|
<h1
|
||||||
|
className="kx-display text-4xl md:text-6xl font-bold mb-5"
|
||||||
|
style={{ wordBreak: 'keep-all', letterSpacing: '-0.02em' }}
|
||||||
|
>
|
||||||
|
검증된 자동화를
|
||||||
|
<br />SaaS로 만듭니다.
|
||||||
|
</h1>
|
||||||
|
<p className="text-base md:text-lg text-white/70 max-w-2xl mx-auto leading-relaxed">
|
||||||
|
현직 엔지니어가 실제로 운영하며 검증한 자동화를 월 구독 제품으로.
|
||||||
|
{isEmpty ? ' 첫 제품을 준비하고 있습니다.' : ''}
|
||||||
|
</p>
|
||||||
|
{isEmpty && (
|
||||||
|
<button
|
||||||
|
onClick={() => {
|
||||||
|
trackCTAClick('packages_waitlist_hero');
|
||||||
|
setModalOpen(true);
|
||||||
|
}}
|
||||||
|
className="kx-btn-primary inline-flex items-center px-7 py-3 rounded-full text-sm mt-8"
|
||||||
|
>
|
||||||
|
출시 알림 받기
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* Available 카탈로그 */}
|
||||||
|
{available.length > 0 && (
|
||||||
|
<section className="py-20 px-6">
|
||||||
|
<div className="max-w-6xl mx-auto grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||||
|
{available.map((pkg) => (
|
||||||
|
<PackageCard key={pkg.slug} pkg={pkg} />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Coming Soon 예고 */}
|
||||||
|
{comingSoon.length > 0 && (
|
||||||
|
<section className="py-20 px-6 bg-white/[0.02] border-t border-white/10">
|
||||||
|
<div className="max-w-6xl mx-auto">
|
||||||
|
<p className="font-mono text-[11px] tracking-widest uppercase text-white/50 mb-4 text-center">
|
||||||
|
Coming Soon
|
||||||
|
</p>
|
||||||
|
<h2 className="kx-display text-2xl md:text-3xl font-bold text-center mb-10">
|
||||||
|
곧 만나볼 제품
|
||||||
|
</h2>
|
||||||
|
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||||
|
{comingSoon.map((pkg) => (
|
||||||
|
<PackageCard key={pkg.slug} pkg={pkg} dimmed />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* 출시 알림 CTA — 항상 노출(빈 상태 아닐 때도 대기자 수집) */}
|
||||||
|
<section className="py-20 px-6 border-t border-white/10">
|
||||||
|
<div className="max-w-3xl mx-auto text-center">
|
||||||
|
<h2 className="kx-display text-2xl md:text-4xl font-bold mb-5">
|
||||||
|
새 제품이 나오면 가장 먼저 알려드릴까요?
|
||||||
|
</h2>
|
||||||
|
<p className="text-base text-white/70 mb-8">
|
||||||
|
관심 분야를 남겨주시면 출시 시 이메일로 안내드립니다. 원하는 자동화 제안도 환영합니다.
|
||||||
|
</p>
|
||||||
|
<button
|
||||||
|
onClick={() => {
|
||||||
|
trackCTAClick('packages_waitlist_cta');
|
||||||
|
setModalOpen(true);
|
||||||
|
}}
|
||||||
|
className="kx-btn-primary inline-flex items-center px-7 py-3 rounded-full text-sm"
|
||||||
|
>
|
||||||
|
출시 알림 받기
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
23
app/page.tsx
23
app/page.tsx
@@ -47,7 +47,6 @@ const CB_CARDS = [
|
|||||||
{ href: '/work/freelance', label: '외주 개발', desc: '맞춤 솔루션 · RPA·API 자동화 포함', key: 'freelance' },
|
{ href: '/work/freelance', label: '외주 개발', desc: '맞춤 솔루션 · RPA·API 자동화 포함', key: 'freelance' },
|
||||||
{ href: '/work/website', label: '웹사이트', desc: '기업·브랜드 사이트', key: 'website' },
|
{ href: '/work/website', label: '웹사이트', desc: '기업·브랜드 사이트', key: 'website' },
|
||||||
{ href: '/work/saju', label: 'AI 사주', desc: '12개 항목 무료 해석', key: 'saju' },
|
{ href: '/work/saju', label: 'AI 사주', desc: '12개 항목 무료 해석', key: 'saju' },
|
||||||
{ href: '/work/blog', label: '블로그 자동화', desc: '수익 엔진 팩', key: 'blog' },
|
|
||||||
];
|
];
|
||||||
|
|
||||||
export default function Home() {
|
export default function Home() {
|
||||||
@@ -93,11 +92,11 @@ export default function Home() {
|
|||||||
className="kx-display text-4xl md:text-6xl lg:text-7xl font-bold mb-5 leading-[1.1]"
|
className="kx-display text-4xl md:text-6xl lg:text-7xl font-bold mb-5 leading-[1.1]"
|
||||||
style={{ wordBreak: 'keep-all', letterSpacing: '-0.02em' }}
|
style={{ wordBreak: 'keep-all', letterSpacing: '-0.02em' }}
|
||||||
>
|
>
|
||||||
현직 엔지니어가 만드는
|
현직 엔지니어가
|
||||||
<br />두 가지.
|
<br />직접 만듭니다.
|
||||||
</h1>
|
</h1>
|
||||||
<p className="text-base md:text-xl text-white/70 leading-relaxed">
|
<p className="text-base md:text-xl text-white/70 leading-relaxed">
|
||||||
AI 제품, 그리고 맞춤 개발.
|
검증된 자동화는 SaaS로. AI 음악 가이드와 커스텀 외주까지.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
@@ -141,7 +140,7 @@ export default function Home() {
|
|||||||
</div>
|
</div>
|
||||||
</Link>
|
</Link>
|
||||||
|
|
||||||
{/* Custom Build 카드 */}
|
{/* 커스텀 외주 카드 */}
|
||||||
<Link
|
<Link
|
||||||
href="/work"
|
href="/work"
|
||||||
onClick={() => trackCTAClick('home_v7_card_work')}
|
onClick={() => trackCTAClick('home_v7_card_work')}
|
||||||
@@ -154,13 +153,13 @@ export default function Home() {
|
|||||||
>
|
>
|
||||||
<div className="relative z-10">
|
<div className="relative z-10">
|
||||||
<p className="font-mono text-[11px] tracking-widest uppercase text-white/60 mb-3">
|
<p className="font-mono text-[11px] tracking-widest uppercase text-white/60 mb-3">
|
||||||
Custom Build
|
Custom Work
|
||||||
</p>
|
</p>
|
||||||
<h2 className="kx-display text-2xl md:text-3xl font-bold text-white mb-2">
|
<h2 className="kx-display text-2xl md:text-3xl font-bold text-white mb-2">
|
||||||
맞춤 개발 사업부
|
커스텀 외주
|
||||||
</h2>
|
</h2>
|
||||||
<p className="text-sm md:text-base text-white/70 mb-4">
|
<p className="text-sm md:text-base text-white/70 mb-4">
|
||||||
외주 · 웹사이트 · AI 사주 · 블로그 자동화
|
외주 · 웹사이트 · AI 사주
|
||||||
</p>
|
</p>
|
||||||
<p className="text-xs text-white/50 mb-5">납품 5건 · 견적 24h 내 답변</p>
|
<p className="text-xs text-white/50 mb-5">납품 5건 · 견적 24h 내 답변</p>
|
||||||
<span className="inline-flex items-center gap-2 text-sm font-bold text-white">
|
<span className="inline-flex items-center gap-2 text-sm font-bold text-white">
|
||||||
@@ -352,12 +351,12 @@ export default function Home() {
|
|||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
{/* 4. Custom Build 섹션 — 4 카드 + 5건 사례 + 견적 CTA */}
|
{/* 4. 커스텀 외주 섹션 — 카드 + 5건 사례 + 견적 CTA */}
|
||||||
<section className="py-24 px-6 bg-black text-white border-b border-white/10">
|
<section className="py-24 px-6 bg-black text-white border-b border-white/10">
|
||||||
<div className="max-w-7xl mx-auto">
|
<div className="max-w-7xl mx-auto">
|
||||||
<div className="text-center mb-14">
|
<div className="text-center mb-14">
|
||||||
<p className="font-mono text-[11px] tracking-widest uppercase text-white/50 mb-4">
|
<p className="font-mono text-[11px] tracking-widest uppercase text-white/50 mb-4">
|
||||||
Custom Build
|
Custom Work
|
||||||
</p>
|
</p>
|
||||||
<h2
|
<h2
|
||||||
className="kx-display text-3xl md:text-5xl font-bold mb-5"
|
className="kx-display text-3xl md:text-5xl font-bold mb-5"
|
||||||
@@ -366,11 +365,11 @@ export default function Home() {
|
|||||||
맞춤 개발이 필요하신가요?
|
맞춤 개발이 필요하신가요?
|
||||||
</h2>
|
</h2>
|
||||||
<p className="text-base md:text-lg text-white/70 max-w-2xl mx-auto leading-relaxed">
|
<p className="text-base md:text-lg text-white/70 max-w-2xl mx-auto leading-relaxed">
|
||||||
7년차 백엔드 개발자가 직접 설계·개발·납품. 외주, 웹사이트, AI 사주, 블로그 자동화까지.
|
7년차 백엔드 개발자가 직접 설계·개발·납품. 외주, 웹사이트, AI 사주까지.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4 mb-12">
|
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4 mb-12">
|
||||||
{CB_CARDS.map((card) => (
|
{CB_CARDS.map((card) => (
|
||||||
<Link
|
<Link
|
||||||
key={card.key}
|
key={card.key}
|
||||||
|
|||||||
@@ -6,8 +6,8 @@ export default function sitemap(): MetadataRoute.Sitemap {
|
|||||||
|
|
||||||
return [
|
return [
|
||||||
{ url: base, lastModified: now, changeFrequency: 'weekly', priority: 1.0 },
|
{ url: base, lastModified: now, changeFrequency: 'weekly', priority: 1.0 },
|
||||||
|
{ url: `${base}/packages`, lastModified: now, changeFrequency: 'weekly', priority: 0.9 },
|
||||||
{ url: `${base}/services/music`, lastModified: now, changeFrequency: 'weekly', priority: 0.95 },
|
{ url: `${base}/services/music`, lastModified: now, changeFrequency: 'weekly', priority: 0.95 },
|
||||||
{ url: `${base}/services/blog`, lastModified: now, changeFrequency: 'weekly', priority: 0.9 },
|
|
||||||
{ url: `${base}/saju`, lastModified: now, changeFrequency: 'monthly', priority: 0.7 },
|
{ url: `${base}/saju`, lastModified: now, changeFrequency: 'monthly', priority: 0.7 },
|
||||||
{ url: `${base}/legal/terms`, lastModified: now, changeFrequency: 'yearly', priority: 0.3 },
|
{ url: `${base}/legal/terms`, lastModified: now, changeFrequency: 'yearly', priority: 0.3 },
|
||||||
{ url: `${base}/legal/refund`, lastModified: now, changeFrequency: 'yearly', priority: 0.3 },
|
{ url: `${base}/legal/refund`, lastModified: now, changeFrequency: 'yearly', priority: 0.3 },
|
||||||
|
|||||||
@@ -1,26 +0,0 @@
|
|||||||
import type { Metadata } from 'next';
|
|
||||||
|
|
||||||
export const metadata: Metadata = {
|
|
||||||
title: '블로그 자동화 솔루션 팩 | 쟁승메이드',
|
|
||||||
description:
|
|
||||||
'쿠팡파트너스·네이버 애드포스트·브랜드커넥트 수익을 자동화하는 프롬프트 조합법 45종 + 구조 템플릿 PDF 80p + 샘플 글 10편. ₩29,000 한 번 결제, 평생 무료 업데이트.',
|
|
||||||
keywords: [
|
|
||||||
'블로그 자동화',
|
|
||||||
'AI 블로그 글쓰기',
|
|
||||||
'쿠팡파트너스 자동화',
|
|
||||||
'애드포스트 수익화',
|
|
||||||
'네이버 블로그 SEO',
|
|
||||||
'ChatGPT 블로그',
|
|
||||||
'블로그 프롬프트',
|
|
||||||
],
|
|
||||||
openGraph: {
|
|
||||||
title: '블로그 자동화 솔루션 팩 | 쟁승메이드',
|
|
||||||
description:
|
|
||||||
'쿠팡파트너스·애드포스트 수익을 자동화하는 프롬프트 + 템플릿 + 샘플. ₩29,000.',
|
|
||||||
url: 'https://jaengseung-made.com/work/blog',
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export default function BlogLayout({ children }: { children: React.ReactNode }) {
|
|
||||||
return children;
|
|
||||||
}
|
|
||||||
@@ -1,243 +0,0 @@
|
|||||||
'use client';
|
|
||||||
|
|
||||||
import { useState } from 'react';
|
|
||||||
import Link from 'next/link';
|
|
||||||
import PurchaseAgreementModal from '@/app/components/PurchaseAgreementModal';
|
|
||||||
|
|
||||||
const PACK_ITEMS = [
|
|
||||||
{
|
|
||||||
icon: '📝',
|
|
||||||
title: '프롬프트 조합법 45종',
|
|
||||||
desc: '상품리뷰 / 정보글 / 후기 / 비교 / 하우투 글별 최적 프롬프트 조합',
|
|
||||||
meta: 'PDF 80p',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
icon: '📐',
|
|
||||||
title: '블로그 글 구조 템플릿 12종',
|
|
||||||
desc: '쿠팡파트너스 · 애드포스트 클릭을 유도하는 검증된 글 구조',
|
|
||||||
meta: 'Notion 템플릿',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
icon: '💰',
|
|
||||||
title: '샘플 글 10편',
|
|
||||||
desc: '실제로 수익이 발생한 블로그 글 전문 + 해설 주석',
|
|
||||||
meta: '.docx · .md',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
icon: '🔍',
|
|
||||||
title: '네이버 SEO 체크리스트',
|
|
||||||
desc: 'C-Rank · D.I.A. 알고리즘 대응 14가지 체크 포인트',
|
|
||||||
meta: 'PDF 20p',
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
const FAQS = [
|
|
||||||
{
|
|
||||||
q: '초보자도 쓸 수 있나요?',
|
|
||||||
a: 'ChatGPT나 Claude 계정만 있으면 됩니다. 프롬프트를 복붙하는 것부터 시작해서 점차 응용하도록 설계했습니다.',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
q: '어떤 플랫폼에 맞나요?',
|
|
||||||
a: '네이버 블로그·티스토리·브런치 모두 대응. 쿠팡파트너스·애드포스트·브랜드커넥트 3가지 수익화 흐름을 모두 다룹니다.',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
q: '업데이트는 얼마나 자주 되나요?',
|
|
||||||
a: '월 1~2회 주요 업데이트. 구매자 전용 Notion 페이지에서 변경 이력과 최신 파일을 제공합니다. 구매 후 12개월간 무료.',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
q: '환불이 되나요?',
|
|
||||||
a: '전자상거래법상 디지털 콘텐츠는 제공 시작 후 환불이 제한됩니다. 구매 전 샘플 미리보기를 충분히 확인해주세요. 파일 손상·전달 불량은 즉시 재전달 또는 환불됩니다.',
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
export default function BlogServicePage() {
|
|
||||||
const [agreeOpen, setAgreeOpen] = useState(false);
|
|
||||||
const [openFaq, setOpenFaq] = useState<number | null>(0);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="min-h-full bg-white">
|
|
||||||
{/* HERO */}
|
|
||||||
<section className="relative overflow-hidden px-6 py-20 lg:px-14 lg:py-28 bg-gradient-to-br from-blue-50 via-white to-sky-50">
|
|
||||||
<div className="relative max-w-5xl mx-auto">
|
|
||||||
<p className="font-mono text-xs text-blue-700/70 tracking-[0.25em] uppercase mb-6">
|
|
||||||
Blog Automation Pack
|
|
||||||
</p>
|
|
||||||
<h1
|
|
||||||
className="text-[2.4rem] md:text-[3.2rem] lg:text-[4rem] font-extrabold leading-[1.08] tracking-tight text-slate-900 mb-6"
|
|
||||||
style={{ wordBreak: 'keep-all' }}
|
|
||||||
>
|
|
||||||
매일 글쓰기 고민,
|
|
||||||
<br />
|
|
||||||
<span className="text-blue-700">AI에게 맡기세요.</span>
|
|
||||||
</h1>
|
|
||||||
<p
|
|
||||||
className="text-slate-600 text-lg md:text-xl leading-relaxed mb-4 max-w-2xl"
|
|
||||||
style={{ wordBreak: 'keep-all' }}
|
|
||||||
>
|
|
||||||
쿠팡파트너스 · 네이버 애드포스트 · 브랜드커넥트 수익을
|
|
||||||
<br />
|
|
||||||
<span className="text-slate-900 font-semibold">자동화하는 프롬프트 · 구조 · 샘플 세트</span>.
|
|
||||||
</p>
|
|
||||||
<div className="inline-flex items-center gap-3 bg-white border border-blue-200 rounded-xl px-5 py-3 mb-8 shadow-sm">
|
|
||||||
<span className="text-3xl font-extrabold text-blue-700 font-mono">₩29,000</span>
|
|
||||||
<span className="text-xs text-slate-500">한 번 결제 · 12개월 무료 업데이트</span>
|
|
||||||
</div>
|
|
||||||
<div className="flex flex-wrap gap-3">
|
|
||||||
<button
|
|
||||||
onClick={() => setAgreeOpen(true)}
|
|
||||||
className="inline-flex items-center gap-2 bg-blue-700 hover:bg-blue-600 text-white px-8 py-4 rounded-xl font-bold text-sm transition-colors shadow-lg shadow-blue-500/30"
|
|
||||||
>
|
|
||||||
구매하기 →
|
|
||||||
</button>
|
|
||||||
<a
|
|
||||||
href="#sample"
|
|
||||||
className="inline-flex items-center gap-2 border border-slate-300 hover:border-blue-500 text-slate-700 hover:text-blue-700 px-8 py-4 rounded-xl font-semibold text-sm transition-all"
|
|
||||||
>
|
|
||||||
샘플 미리보기
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
{/* PAIN POINTS */}
|
|
||||||
<section className="px-6 py-16 lg:px-14 bg-white">
|
|
||||||
<div className="max-w-5xl mx-auto">
|
|
||||||
<h2
|
|
||||||
className="text-2xl md:text-3xl font-extrabold text-slate-900 mb-10 text-center"
|
|
||||||
style={{ wordBreak: 'keep-all' }}
|
|
||||||
>
|
|
||||||
이런 분들을 위한 팩입니다.
|
|
||||||
</h2>
|
|
||||||
<div className="grid sm:grid-cols-3 gap-5">
|
|
||||||
{[
|
|
||||||
{ icon: '🕐', title: '매일 1시간+', desc: '글 소재·구성에 시간 다 쓰고 수익은 제자리' },
|
|
||||||
{ icon: '📉', title: '수익화 6개월+', desc: '블로그 키워놓고도 수익 구조가 안 잡힘' },
|
|
||||||
{ icon: '🤖', title: 'AI 글은 어색', desc: 'ChatGPT 그대로 복붙하면 바로 들통' },
|
|
||||||
].map((p) => (
|
|
||||||
<div key={p.title} className="border border-slate-200 rounded-2xl p-6 bg-slate-50/50">
|
|
||||||
<div className="text-3xl mb-3">{p.icon}</div>
|
|
||||||
<h3 className="font-extrabold text-slate-900 mb-2">{p.title}</h3>
|
|
||||||
<p className="text-sm text-slate-600 leading-relaxed" style={{ wordBreak: 'keep-all' }}>
|
|
||||||
{p.desc}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
{/* PACK CONTENT */}
|
|
||||||
<section id="sample" className="px-6 py-20 lg:px-14 bg-slate-50">
|
|
||||||
<div className="max-w-5xl mx-auto">
|
|
||||||
<p className="font-mono text-xs text-blue-700/70 tracking-widest uppercase mb-2">
|
|
||||||
Pack Contents
|
|
||||||
</p>
|
|
||||||
<h2 className="text-2xl md:text-3xl font-extrabold text-slate-900 mb-10">
|
|
||||||
구성품 4종
|
|
||||||
</h2>
|
|
||||||
<div className="grid md:grid-cols-2 gap-5">
|
|
||||||
{PACK_ITEMS.map((it) => (
|
|
||||||
<div
|
|
||||||
key={it.title}
|
|
||||||
className="flex gap-4 bg-white border border-slate-200 rounded-2xl p-6 hover:border-blue-400 transition-colors"
|
|
||||||
>
|
|
||||||
<div className="text-3xl flex-shrink-0">{it.icon}</div>
|
|
||||||
<div>
|
|
||||||
<div className="flex items-center gap-2 mb-1.5">
|
|
||||||
<h3 className="font-extrabold text-slate-900">{it.title}</h3>
|
|
||||||
<span className="text-[10px] font-mono text-blue-700 bg-blue-100 px-1.5 py-0.5 rounded">
|
|
||||||
{it.meta}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<p className="text-sm text-slate-600 leading-relaxed" style={{ wordBreak: 'keep-all' }}>
|
|
||||||
{it.desc}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Sample preview */}
|
|
||||||
<div className="mt-12 bg-white border-2 border-dashed border-blue-300 rounded-2xl p-8 relative overflow-hidden">
|
|
||||||
<span className="absolute top-4 right-4 text-[10px] font-bold text-blue-700 bg-blue-100 px-2 py-1 rounded">
|
|
||||||
샘플 미리보기
|
|
||||||
</span>
|
|
||||||
<h4 className="font-extrabold text-slate-900 mb-3">프롬프트 예시 · 상품 리뷰 글 자동 생성</h4>
|
|
||||||
<pre className="text-xs font-mono text-slate-700 bg-slate-50 rounded-lg p-4 overflow-x-auto leading-relaxed">{`당신은 [카테고리] 전문 블로거입니다.
|
|
||||||
아래 상품의 [핵심 장점 3개]와 [주의점 1개]를 기반으로
|
|
||||||
C-Rank 알고리즘에 최적화된 1,200자 리뷰 글을 작성하세요.
|
|
||||||
|
|
||||||
[구조]
|
|
||||||
1. 후킹 도입 (공감형 질문)
|
|
||||||
2. 상품 요약 (스펙 표)
|
|
||||||
3. 실사용 관점 장점·단점
|
|
||||||
4. 대안 비교 (쿠팡 링크 삽입 지점: {LINK})
|
|
||||||
5. 결론 + 재질문 유도
|
|
||||||
|
|
||||||
[톤앤매너] 친근한 존댓말, 광고 느낌 최소화 ...`}</pre>
|
|
||||||
<p className="text-xs text-slate-500 mt-4">
|
|
||||||
실제 팩에는 카테고리별 45종의 프롬프트와 최적화 파라미터가 포함됩니다.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
{/* FAQ */}
|
|
||||||
<section className="px-6 py-20 lg:px-14 bg-white">
|
|
||||||
<div className="max-w-3xl mx-auto">
|
|
||||||
<h2 className="text-2xl md:text-3xl font-extrabold text-slate-900 mb-8 text-center">
|
|
||||||
자주 묻는 질문
|
|
||||||
</h2>
|
|
||||||
<div className="space-y-3">
|
|
||||||
{FAQS.map((f, i) => (
|
|
||||||
<div key={i} className="border border-slate-200 rounded-xl overflow-hidden">
|
|
||||||
<button
|
|
||||||
onClick={() => setOpenFaq(openFaq === i ? null : i)}
|
|
||||||
className="w-full flex items-center justify-between px-5 py-4 text-left hover:bg-slate-50 transition-colors"
|
|
||||||
>
|
|
||||||
<span className="font-bold text-slate-900 text-sm">{f.q}</span>
|
|
||||||
<span className={`text-blue-700 text-xl transition-transform ${openFaq === i ? 'rotate-45' : ''}`}>
|
|
||||||
+
|
|
||||||
</span>
|
|
||||||
</button>
|
|
||||||
{openFaq === i && (
|
|
||||||
<div className="px-5 pb-5 text-sm text-slate-600 leading-relaxed" style={{ wordBreak: 'keep-all' }}>
|
|
||||||
{f.a}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
{/* FINAL CTA */}
|
|
||||||
<section className="px-6 py-20 lg:px-14 bg-gradient-to-br from-blue-700 to-blue-900">
|
|
||||||
<div className="max-w-3xl mx-auto text-center">
|
|
||||||
<h2 className="text-3xl md:text-4xl font-extrabold text-white mb-4" style={{ wordBreak: 'keep-all' }}>
|
|
||||||
오늘부터 블로그 수익 자동화.
|
|
||||||
</h2>
|
|
||||||
<p className="text-blue-100 text-lg mb-8">₩29,000 한 번 결제 · 평생 업데이트</p>
|
|
||||||
<button
|
|
||||||
onClick={() => setAgreeOpen(true)}
|
|
||||||
className="inline-flex items-center gap-2 bg-white text-blue-700 hover:bg-blue-50 px-10 py-4 rounded-xl font-extrabold text-base transition-colors shadow-xl"
|
|
||||||
>
|
|
||||||
지금 구매하기 →
|
|
||||||
</button>
|
|
||||||
<p className="text-blue-200/80 text-xs mt-6">
|
|
||||||
<Link href="/legal/refund" className="underline hover:text-white">환불 정책</Link>
|
|
||||||
{' · '}
|
|
||||||
<Link href="/legal/terms" className="underline hover:text-white">이용약관</Link>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<PurchaseAgreementModal
|
|
||||||
isOpen={agreeOpen}
|
|
||||||
onClose={() => setAgreeOpen(false)}
|
|
||||||
productName="블로그 자동화 솔루션 팩"
|
|
||||||
price="₩29,000"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
import type { Metadata } from 'next';
|
import type { Metadata } from 'next';
|
||||||
|
|
||||||
export const metadata: Metadata = {
|
export const metadata: Metadata = {
|
||||||
title: 'Custom Build — 맞춤 개발 사업부',
|
title: '커스텀 외주 — 맞춤 개발',
|
||||||
description: '7년차 백엔드 개발자가 직접 설계·개발·납품. 외주 · 웹사이트 · AI 사주 · 블로그 자동화.',
|
description: '7년차 백엔드 개발자가 직접 설계·개발·납품. 외주 · 웹사이트 · AI 사주.',
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function WorkLayout({ children }: { children: React.ReactNode }) {
|
export default function WorkLayout({ children }: { children: React.ReactNode }) {
|
||||||
|
|||||||
@@ -25,12 +25,6 @@ const CARDS = [
|
|||||||
desc: 'AI 사주팔자 + 12개 항목 해석 (무료)',
|
desc: 'AI 사주팔자 + 12개 항목 해석 (무료)',
|
||||||
key: 'saju',
|
key: 'saju',
|
||||||
},
|
},
|
||||||
{
|
|
||||||
href: '/work/blog',
|
|
||||||
label: '블로그 자동화',
|
|
||||||
desc: '수익 엔진 팩 · 자동화 마케팅 콘텐츠',
|
|
||||||
key: 'blog',
|
|
||||||
},
|
|
||||||
];
|
];
|
||||||
|
|
||||||
export default function WorkHub() {
|
export default function WorkHub() {
|
||||||
@@ -58,22 +52,22 @@ export default function WorkHub() {
|
|||||||
<div className="absolute inset-0 bg-gradient-to-b from-[#060e20] to-black pointer-events-none" />
|
<div className="absolute inset-0 bg-gradient-to-b from-[#060e20] to-black pointer-events-none" />
|
||||||
<div className="relative z-10 max-w-3xl mx-auto text-center">
|
<div className="relative z-10 max-w-3xl mx-auto text-center">
|
||||||
<p className="font-mono text-[11px] tracking-widest uppercase text-white/50 mb-4">
|
<p className="font-mono text-[11px] tracking-widest uppercase text-white/50 mb-4">
|
||||||
Custom Build
|
Custom Work
|
||||||
</p>
|
</p>
|
||||||
<h1
|
<h1
|
||||||
className="kx-display text-4xl md:text-6xl font-bold mb-5"
|
className="kx-display text-4xl md:text-6xl font-bold mb-5"
|
||||||
style={{ wordBreak: 'keep-all', letterSpacing: '-0.02em' }}
|
style={{ wordBreak: 'keep-all', letterSpacing: '-0.02em' }}
|
||||||
>
|
>
|
||||||
맞춤 개발 사업부
|
커스텀 외주
|
||||||
</h1>
|
</h1>
|
||||||
<p className="text-base md:text-lg text-white/70 max-w-2xl mx-auto leading-relaxed">
|
<p className="text-base md:text-lg text-white/70 max-w-2xl mx-auto leading-relaxed">
|
||||||
7년차 백엔드 개발자가 직접 설계·개발·납품. 외주, 웹사이트, AI 사주, 블로그 자동화까지.
|
7년차 백엔드 개발자가 직접 설계·개발·납품. 외주, 웹사이트, AI 사주까지.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section className="py-20 px-6">
|
<section className="py-20 px-6">
|
||||||
<div className="max-w-6xl mx-auto grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4">
|
<div className="max-w-6xl mx-auto grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||||
{CARDS.map((c) => (
|
{CARDS.map((c) => (
|
||||||
<Link
|
<Link
|
||||||
key={c.key}
|
key={c.key}
|
||||||
|
|||||||
266
docs/superpowers/plans/2026-05-31-saas-pivot-migration.md
Normal file
266
docs/superpowers/plans/2026-05-31-saas-pivot-migration.md
Normal file
@@ -0,0 +1,266 @@
|
|||||||
|
# 쟁승메이드 SaaS 전환 — 단계별 마이그레이션 계획
|
||||||
|
|
||||||
|
> **For agentic workers:** 이 문서는 **단계별 마이그레이션 계획**이다(엄격한 TDD task-by-task 아님). Phase 단위로 목표·변경 항목·영향 파일·검증·완료조건을 정의한다. 각 Phase는 독립적으로 배포 가능한 단위가 되도록 설계했다. 체크박스(`- [ ]`)로 진행을 추적한다.
|
||||||
|
|
||||||
|
**Goal:** 2026-05-29 정체성 재정의("Music + Custom Build 두 사업부" → **"SaaS 제품 판매 + 커스텀 외주 병행"**)를 실제 코드베이스에 단계적으로 반영한다.
|
||||||
|
|
||||||
|
**Architecture:** 기존 IA(상단 `/music` · `/work` 2분할)를 **SaaS 패키지(월 구독) + 커스텀 외주(1회 직판)** 구조로 재편한다. 블로그 자동화는 완전 제거, **AI 음악은 구독으로 가지 않고 "AI 음악 생성 개발 가이드 패키지" 단품으로 디벨롭**, 사주는 별도 도메인으로 분리한다. 구독 인프라(`subscriptions` 테이블 · `subscription-expiry` cron · `products.ts` 의 `monthly` 타입)는 **이미 존재**하므로, 향후 메이킹 검증 자동화가 나올 때 재활용한다(음악 이탈로 첫 구독 패키지는 미정).
|
||||||
|
|
||||||
|
**Tech Stack:** Next.js 16 (App Router, TS) · Tailwind v4 · Supabase(Postgres + RLS) · Resend · Vercel · Toss/Portone 결제
|
||||||
|
|
||||||
|
**상태:** 초안 (2026-05-31). Phase 1·2는 즉시 실행 가능. Phase 3·4는 "7월 본격" 의존. Phase 5는 대상 도메인 결정 후 착수.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 0. 배경 & 출처
|
||||||
|
|
||||||
|
- 의사결정 출처: Obsidian 위키 `wiki/사업-쟁승메이드.md` "정체성 재정의 (2026-05-29)" 섹션 + [[프로젝트-쟁승메이드-Co]].
|
||||||
|
- 4단계 brainstorming 결과(박재오 명시):
|
||||||
|
- 정체성: **SaaS 제품 판매 + 커스텀 외주 병행**
|
||||||
|
- 블로그 자동화: **❌ 완전 삭제** (2026-05-17 폐기 결정의 코드 흔적 정리)
|
||||||
|
- AI 음악 39k/99k/149k 1회 직판 → ~~SaaS 월 구독 흡수~~ → **2026-05-31 정정: 구독 폐기, "AI 음악 생성 개발 가이드 패키지" 단품으로 디벨롭**
|
||||||
|
- 사주 카탈로그: **별도 도메인 마이그레이션** ([[프로젝트-쟁승메이드-Co]]의 갈래 2 도전 줄기)
|
||||||
|
- 외주: **❌ 크몽·숨고 절대 안 함** → 인스타 카드뉴스(Hedgy75) 직접 유입
|
||||||
|
- 수익 모델: **1(SaaS 구독) 메인 + 5(외주 직판) 보조**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. 현재 상태 진단 (코드 기준 갭 분석)
|
||||||
|
|
||||||
|
| 영역 | 현재 코드 상태 | 목표 | Phase |
|
||||||
|
|------|--------------|------|-------|
|
||||||
|
| 블로그 자동화 | `/work/blog` 라우트 + 9개 파일 참조 + JSON-LD Offer + sitemap 잔존 | 완전 제거 | **P1** |
|
||||||
|
| IA / 네비게이션 | TopNav `Music`·`Custom Build`, footer 2그룹 | SaaS 제품 / 커스텀 외주 구조 | **P2** |
|
||||||
|
| 외부 마켓 문구 | `STRATEGY.md` 등에 프리랜서 마켓 전제 | 크몽·숨고 미사용 + 인스타 유입 명시 | **P2** |
|
||||||
|
| SaaS 패키지 카탈로그 | 없음 (개별 서비스 페이지만) | 월 구독 패키지 카탈로그 (첫 패키지 후보 미정) | **P3 (대기)** |
|
||||||
|
| 구독 인프라 | `subscriptions` 테이블·cron·`products.ts(monthly)` **존재** | 보존, 향후 검증 자동화에 재활용 | P3 |
|
||||||
|
| AI 음악 | `/music/packs` 3티어 1회 결제 | **단품 유지 + "개발 가이드 패키지"로 디벨롭** (구독 ❌) | **P4** |
|
||||||
|
| 사주 | `/work/saju` + API 4종 + lib 3종, 사이트 내장 | 별도 도메인 분리 + 301 | **P5** |
|
||||||
|
|
||||||
|
### 죽은 코드 / 즉시 정리 가능
|
||||||
|
- `lib/blog-tools/generator.ts` — **어디서도 import 안 됨**(grep 확인). 삭제 안전.
|
||||||
|
|
||||||
|
### 의사결정 — 2026-05-31 박재오 확정 (5건)
|
||||||
|
> 2026-05-31 박재오 결정으로 보류 5항목을 아래와 같이 해소·갱신.
|
||||||
|
|
||||||
|
1. ✅ **음악 = 구독 안 함** — 월 구독 폐기. **"AI 활용 음악 생성 개발 가이드 패키지"로 디벨롭하여 단품(1회) 판매 유지.** → P4 재정의(구독 전환 → 단품 디벨롭).
|
||||||
|
2. ✅ **기존 1회 구매자 = 0명** — 실제 구매 이력 없음. grandfathering 불필요. "구매자 없음" 사실만 기록하고 **다시 논점으로 떠오르지 않도록** 처리. → P4.
|
||||||
|
3. ⏸ **사주 분리 대상 도메인 = 대기** — 도메인 미구매. **구매 완료 전까지 P5 착수 보류.** 도메인 확정이 P5 유일 블로커.
|
||||||
|
4. 🔍 **사주 DB = 기존 Supabase 기본 + NAS 셀프호스팅 검토** — 일단 현 Supabase 유지. 단, **NAS(Postgres) 자체 호스팅으로 옮기는 안을 별도 검토**(비용·백업·외부 접근·RLS 대체). → P5 내 검토 작업으로 편입.
|
||||||
|
5. ✅ **사주 결제 이력 = 전부 이전** — `orders`/`subscriptions`의 사주(`saju_detail` 등) 결제·주문 데이터를 **신규 도메인 DB로 전량 마이그레이션**. → P5.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 1 — 블로그 자동화 완전 삭제 ✅ (2026-05-31 완료)
|
||||||
|
|
||||||
|
**목표:** `/work/blog` 라우트와 모든 코드/메타/마케팅 흔적을 제거하고, 기존 URL은 안전하게 처리한다.
|
||||||
|
|
||||||
|
**영향 파일 (모두 확인됨):**
|
||||||
|
- 삭제: `app/work/blog/page.tsx`, `app/work/blog/layout.tsx`, `app/work/blog/` 폴더
|
||||||
|
- 삭제: `lib/blog-tools/generator.ts` (+ 빈 `lib/blog-tools/` 폴더)
|
||||||
|
- 수정: `app/components/PublicShell.tsx` — footer "Custom Build" 그룹의 `블로그 자동화` 링크 제거
|
||||||
|
- 수정: `app/work/page.tsx` — line 29~32 `blog` 카드 객체 제거, line 70 카피에서 "블로그 자동화" 제거
|
||||||
|
- 수정: `app/page.tsx` — line 50 `blog` 카드 제거, line 163·369 카피에서 "블로그 자동화" 제거
|
||||||
|
- 수정: `app/layout.tsx` — line 30 keywords "블로그 자동화" 제거, line 77·84 description 수정, line 96 JSON-LD `블로그 자동화 솔루션 팩` Offer 객체 제거
|
||||||
|
- 수정: `app/legal/refund/page.tsx` — line 21 환불 대상 목록에서 "블로그 자동화 솔루션 팩" 제거
|
||||||
|
- 수정: `app/sitemap.ts` — line 10 `/services/blog` 엔트리 제거 (+ `/work/blog` 엔트리 있으면 제거)
|
||||||
|
- 수정: `next.config.ts` — `/services/blog → /work/blog` 리다이렉트 처리 (아래 정책 참조)
|
||||||
|
- 유지(변경 X): `app/work/website/page.tsx:106` "개인 블로그…" = 웹사이트 제작 예시 카피일 뿐 블로그 자동화 상품 아님. `app/admin/marketing/page.tsx:79·81` "블로그/SNS" = 배너 활용처 설명. 둘 다 **건드리지 않음**.
|
||||||
|
|
||||||
|
**리다이렉트 정책:**
|
||||||
|
- `next.config.ts`의 `{ source: '/services/blog', destination: '/work/blog', permanent: true }` → `/work/blog`가 사라지므로 **목적지를 `/work`로 변경** (제품 라인 허브로 안내). 410(Gone)보다 301→`/work`가 SEO·UX상 안전.
|
||||||
|
|
||||||
|
**작업 체크리스트:**
|
||||||
|
- [ ] 1-1. `app/work/blog/` 폴더 전체 삭제
|
||||||
|
- [ ] 1-2. `lib/blog-tools/` 폴더 전체 삭제
|
||||||
|
- [ ] 1-3. 위 "수정" 파일 7개에서 블로그 참조 제거 (정확 라인은 작업 시점 재확인 — 리팩터로 이동 가능)
|
||||||
|
- [ ] 1-4. `next.config.ts` 리다이렉트 목적지 `/services/blog → /work` 로 변경
|
||||||
|
- [ ] 1-5. 검증: `grep -rn "work/blog\|블로그 자동화\|blog-tools" app lib components` → **0건** (단 1-3 유지 항목 제외)
|
||||||
|
- [ ] 1-6. 검증: `npm run build` 성공 (타입·링크 깨짐 없음)
|
||||||
|
- [ ] 1-7. 검증: 로컬에서 `/work/blog` 접속 시 404, `/services/blog` 접속 시 `/work`로 301
|
||||||
|
- [ ] 1-8. 커밋: `chore(blog): /work/blog 라우트·참조·메타 완전 제거 (2026-05-29 재정의)`
|
||||||
|
|
||||||
|
**완료 조건:** 블로그 자동화의 라우트·UI 링크·구조화 데이터·sitemap·환불약관·죽은 lib가 모두 사라지고, 빌드 성공 + 기존 URL 301 처리됨.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 2 — IA 재편: "SaaS 제품 + 커스텀 외주 병행" ✅ (2026-05-31 완료)
|
||||||
|
|
||||||
|
> **실제 구현 메모:** TopNav를 2탭이 아니라 **3탭(SaaS 제품 `/packages` · AI 음악 `/music` · 커스텀 외주 `/work`)**으로 구성했다. 음악이 단품으로 분리되면서 "SaaS 제품" 라벨이 음악을 가리키는 모순을 피하고 음악 발견성을 유지하기 위함(박재오 결정). Footer도 동일 3그룹 + Legal로 정렬.
|
||||||
|
|
||||||
|
**목표:** 상단/푸터 네비게이션과 홈 카피를 새 정체성에 맞게 재편한다. 코드 라우트 대규모 이동 없이 **라벨·그룹핑·카피 수준**에서 먼저 정렬한다(라우트 실이동은 P3에서).
|
||||||
|
|
||||||
|
**핵심 결정:** `/music`·`/work` 라우트 경로는 **유지**(301 리다이렉트 누적 회피). 사용자 노출 라벨만 SaaS/커스텀 프레이밍으로 변경.
|
||||||
|
|
||||||
|
| 위치 | 현재 라벨 | 변경안 |
|
||||||
|
|------|---------|--------|
|
||||||
|
| TopNav | `Music` / `Custom Build` | `SaaS 제품` / `커스텀 외주` (경로는 `/music`·`/work` 유지) |
|
||||||
|
| Footer 그룹 | `Music` / `Custom Build` | `SaaS 제품` / `커스텀 외주` |
|
||||||
|
| 홈 Hero/카피 | "두 사업부" 뉘앙스 | "검증된 자동화를 SaaS로 + 필요 시 커스텀 외주" 한 문장 |
|
||||||
|
|
||||||
|
**영향 파일:**
|
||||||
|
- 수정: `app/components/TopNav.tsx` — line 10~11 `NAV` 배열 라벨 (`Music`→`SaaS 제품`, `Custom Build`→`커스텀 외주`). `href`는 유지.
|
||||||
|
- 수정: `app/components/PublicShell.tsx` — footer 그룹 헤더 라벨 2곳
|
||||||
|
- 수정: `app/page.tsx` — Hero·소개 카피 (line 163·369 인근, 새 정체성 1문장)
|
||||||
|
- 수정: `app/work/page.tsx` — 허브 카피 (line 70 인근)
|
||||||
|
- 수정(문구): `STRATEGY.md` — "프리랜서 마켓/숨고/크몽" 전제 문장에 **"크몽·숨고 미사용 — 인스타 카드뉴스(Hedgy75) 직접 유입"** 정책 주석 추가 (전체 재작성은 P6/별도)
|
||||||
|
|
||||||
|
**작업 체크리스트:**
|
||||||
|
- [ ] 2-1. TopNav·Footer 라벨 SaaS/커스텀 프레이밍으로 변경 (경로 불변)
|
||||||
|
- [ ] 2-2. 홈·work 허브 카피를 새 정체성 1문장으로 정렬
|
||||||
|
- [ ] 2-3. `STRATEGY.md`에 외주 유입 채널 정책(크몽·숨고 ❌ / 인스타 ⭕) 주석 추가
|
||||||
|
- [ ] 2-4. 검증: `grep -rn "Custom Build\|두 사업부" app components` → 의도된 잔존만 남음
|
||||||
|
- [ ] 2-5. 검증: `npm run build` 성공 + 헤더/푸터 라벨 육안 확인
|
||||||
|
- [ ] 2-6. 커밋: `refactor(ia): 네비게이션·카피를 SaaS+커스텀 외주 정체성으로 재편`
|
||||||
|
|
||||||
|
**완료 조건:** 방문자가 보는 1차 프레이밍이 "SaaS 제품 + 커스텀 외주"로 통일되고, 외부 마켓 미사용 정책이 문서에 명시됨. 라우트 경로는 불변이라 회귀 위험 최소.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 3 — SaaS 패키지 카탈로그 구조 도입 🟡 (골격 구현됨 — 첫 패키지만 대기)
|
||||||
|
|
||||||
|
**목표:** "검증된 자동화 → SaaS 월 구독 패키지" 구조를 담을 **카탈로그 페이지 + 데이터 모델**을 만든다. 결제는 기존 `subscriptions` 인프라에 연결한다.
|
||||||
|
|
||||||
|
> 🟡 **상태: 골격 구현 완료(2026-05-31), 첫 제품만 대기.** 박재오 결정으로 P3의 **확장 골격을 먼저 구현**했다(빈 카탈로그도 깨지지 않고, 제품 추가 시 자동 노출). 남은 것은 **첫 SaaS 제품 1개 확정 + 등록**뿐이다. 메이킹 스페이스에서 검증된 자동화가 나오면 `SAAS_CATALOG` 배열에 항목 1개 추가 + `lib/products.ts`에 `monthly` 상품 추가하면 된다. 구독 인프라는 보존됨.
|
||||||
|
>
|
||||||
|
> **구현 완료분 (2026-05-31):**
|
||||||
|
> - 경로명 **`/packages` 확정** (결정 포인트 해소).
|
||||||
|
> - `lib/saas-catalog.ts` — `SaasCatalogItem` 데이터 모델 + `SAAS_CATALOG` 배열(현재 빈 배열) + `getAvailablePackages()`/`getComingSoonPackages()` 헬퍼.
|
||||||
|
> - `app/packages/page.tsx` + `layout.tsx` — available 카드 그리드 / coming_soon 흐린 카드 / **available 0개일 때 예고 히어로 + "출시 알림 받기"**(기존 `ContactModal` 재사용, `service='SaaS 출시 알림 신청'`).
|
||||||
|
> - TopNav·Footer "SaaS 제품" → `/packages` 연결, sitemap 등록.
|
||||||
|
>
|
||||||
|
> **남은 것 (첫 제품 확정 후):** `SAAS_CATALOG`에 `status:'available'` 항목 추가, `lib/products.ts` `monthly` 상품 추가, `/api/subscription` product_id 처리 확인, 결제→`subscriptions` row→mypage→cron 만료 end-to-end 검증, `/packages/[slug]` 상세(필요 시).
|
||||||
|
|
||||||
|
**전제:** 메이킹 검증 자동화 1개 확정 후 착수. (음악은 더 이상 첫 패키지 아님 — P4 단품 라인으로 분리.)
|
||||||
|
|
||||||
|
**영향 파일:**
|
||||||
|
- 신규: `app/saas/page.tsx` (또는 `/packages`) — SaaS 패키지 카탈로그(월 구독 카드 그리드). 경로명 확정 필요(아래 결정 포인트).
|
||||||
|
- 신규: `app/saas/layout.tsx` — 메타데이터
|
||||||
|
- 수정: `lib/products.ts` — SaaS 패키지 상품 정의(`type: 'monthly'`) 추가. 기존 `monthly` 타입 패턴(`stock_starter_monthly` 등) 그대로 따름.
|
||||||
|
- 수정: `app/api/subscription/route.ts` — 신규 패키지 product_id 처리(기존 흐름 재활용 확인)
|
||||||
|
- 수정: `app/components/TopNav.tsx` — `SaaS 제품` 항목 목적지를 `/music` → `/saas`로 전환(P4 완료 시점에 맞춰)
|
||||||
|
- 참조(변경 X): `supabase/migrations/004_subscriptions.sql`, `app/api/cron/subscription-expiry/route.ts` — 그대로 사용
|
||||||
|
|
||||||
|
**결정 포인트:**
|
||||||
|
- 경로명: `/saas` vs `/packages` vs `/products` — 1개 확정. (권장: `/packages` — 한국어 노출 "패키지"와 자연 매칭)
|
||||||
|
- 첫 패키지 = 음악 1개로 시작할지, 메이킹 검증 자동화 1~2개를 동시 등판할지.
|
||||||
|
|
||||||
|
**작업 체크리스트:**
|
||||||
|
- [ ] 3-1. 경로명·첫 패키지 범위 확정 (박재오)
|
||||||
|
- [ ] 3-2. `lib/products.ts`에 SaaS 패키지 상품 추가(`monthly`)
|
||||||
|
- [ ] 3-3. 카탈로그 페이지 + layout 생성 (구독 카드 → `subscription` API 연결)
|
||||||
|
- [ ] 3-4. 기존 `subscriptions` 테이블·만료 cron으로 신규 패키지 생명주기 동작 확인
|
||||||
|
- [ ] 3-5. 검증: 테스트 결제 → `subscriptions` row 생성 → mypage 노출 → cron 만료 처리
|
||||||
|
- [ ] 3-6. 검증: `npm run build` 성공
|
||||||
|
- [ ] 3-7. 커밋: `feat(saas): SaaS 월 구독 패키지 카탈로그 + 구독 연결`
|
||||||
|
|
||||||
|
**완료 조건:** SaaS 패키지를 카탈로그에서 구독 결제 → `subscriptions`에 기록 → mypage에서 확인 → 만료 cron 동작까지 end-to-end 성립.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 4 — AI 음악: "음악 생성 개발 가이드 패키지" 단품으로 디벨롭 ✅ (2026-05-31 완료)
|
||||||
|
|
||||||
|
**목표:** `/music/packs`를 **월 구독으로 전환하지 않는다.** 기존 39k/99k/149k **1회 결제 구조를 유지**하되, 상품의 정체성을 **"AI를 활용한 음악 생성 개발 가이드 패키지"**로 디벨롭(포지셔닝·콘텐츠 강화)한다. 구독 인프라는 건드리지 않는다.
|
||||||
|
|
||||||
|
> 2026-05-31 결정: 구독 폐기. 음악은 SaaS 라인이 아니라 **디지털 상품(가이드 패키지) 단품 라인**으로 확정.
|
||||||
|
|
||||||
|
**기존 구매자 처리 (결정 #2):** 실제 구매자 **0명** 확인. grandfathering·마이그레이션 불필요. → 본 Phase에서 **"기존 1회 구매자 0명 — 별도 처리 없음"을 명시 기록**하고, 향후 계획에서 이 논점이 다시 떠오르지 않도록 종결한다(이 문장이 그 종결 기록).
|
||||||
|
|
||||||
|
**영향 파일:**
|
||||||
|
- 수정: `app/music/packs/page.tsx` — `TIERS`의 가격/주기는 **1회 결제 유지**. `name`·`desc`·`features`를 "개발 가이드 패키지" 정체성으로 재서술(예: "프롬프트 조합·반복 가능한 워크플로우 설계 가이드"). line 295~296 `productName`을 "AI 음악 생성 개발 가이드 · {티어}"로.
|
||||||
|
- (선택) 수정: `app/music/packs/layout.tsx` — 메타 title/description을 "음악 생성 개발 가이드"로 갱신
|
||||||
|
- 수정: `app/layout.tsx` — JSON-LD 음악 Offer의 `Product.name`/`description`을 "개발 가이드 패키지"로 갱신(가격·1회 결제 유지)
|
||||||
|
- 수정: `app/legal/refund/page.tsx` — "AI 음악 마스터 구조 팩" 표기를 신규 패키지명과 일치하게 정리(1회 디지털 콘텐츠 환불 규정은 유지)
|
||||||
|
- 변경 **불가**(보존): `lib/products.ts`의 `monthly` 패턴, `subscriptions` 테이블, `subscription-expiry` cron — 음악과 무관하게 P3용으로 보존
|
||||||
|
- 콘텐츠 작업: `CONTENT/` 하위 음악 패키지 카피·가이드 본문 디벨롭(코드 외 산출물)
|
||||||
|
|
||||||
|
**작업 체크리스트:**
|
||||||
|
- [ ] 4-1. 패키지 정체성·티어별 신규 카피/구성 확정 (박재오 — "개발 가이드" 관점 features)
|
||||||
|
- [ ] 4-2. `/music/packs` `TIERS` 메타(name/desc/features) 재서술 — **가격·1회 결제 유지**
|
||||||
|
- [ ] 4-3. `layout.tsx` JSON-LD + `music/packs/layout.tsx` 메타를 가이드 패키지로 갱신
|
||||||
|
- [ ] 4-4. `refund/page.tsx` 상품명 표기 정합화
|
||||||
|
- [ ] 4-5. "기존 1회 구매자 0명 — 처리 없음" 종결 기록(본 문서로 충족)
|
||||||
|
- [ ] 4-6. 검증: `grep -rn "월 구독\|구독.*음악\|music.*monthly" app lib` → 음악-구독 결합 흔적 0건
|
||||||
|
- [ ] 4-7. 검증: `npm run build` 성공 + `/music/packs` 1회 결제 흐름 정상
|
||||||
|
- [ ] 4-8. 커밋: `feat(music): 음악 팩을 "AI 음악 생성 개발 가이드 패키지" 단품으로 디벨롭 (구독 폐기)`
|
||||||
|
|
||||||
|
**완료 조건:** 음악 팩이 "개발 가이드 패키지" 정체성으로 1회 판매되고, 구독 결합 흔적이 코드/메타/약관에 없으며, 기존 구매자 0명 사실이 종결 기록됨.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 5 — 사주 카탈로그 별도 도메인 분리
|
||||||
|
|
||||||
|
**목표:** `/work/saju` 및 사주 전용 API·lib를 **별도 도메인 프로젝트**로 이관하고, 현 사이트에서 제거 + 외부 안내/301 처리한다.
|
||||||
|
|
||||||
|
> ⏸ **상태: 대기 (블로커 = 도메인 미구매).** 2026-05-31 기준 분리 대상 도메인을 아직 구매하지 않음. **도메인 구매 완료가 P5 착수의 단일 선결 조건.** 그 전까지 사주는 현 사이트에 그대로 둔다.
|
||||||
|
|
||||||
|
**DB 방침 (결정 #4):** 기본은 **기존 Supabase 유지**. 단, **NAS(Postgres) 자체 호스팅 이전 안을 별도 검토**한다 — 검토 포인트: 월 비용 절감 vs. 외부 접근성/가용성, 백업·복구, Supabase RLS·Auth 대체(직접 구현 부담), Vercel→NAS 네트워크 레이턴시. 검토 결과로 "Supabase 유지" 또는 "NAS 이전" 택1.
|
||||||
|
|
||||||
|
**결제 이력 방침 (결정 #5):** `orders`/`subscriptions`의 사주 관련 결제·주문 데이터를 **신규 도메인 DB로 전량 이전**(누락 없이).
|
||||||
|
|
||||||
|
**전제:** 도메인 구매 완료(블로커). DB 호스팅(Supabase vs NAS)은 착수 시점에 검토 결과로 확정.
|
||||||
|
|
||||||
|
**이관 대상 (현 코드 기준 전수):**
|
||||||
|
- 라우트: `app/work/saju/` 전체 (`page.tsx`, `input/`, `result/`, `components/SajuForm.tsx`, `result/SajuAISection.tsx`, `result/SajuFortuneSection.tsx`, `layout.tsx`)
|
||||||
|
- API: `app/api/saju/analyze/`, `app/api/saju/calculate/`, `app/api/saju/lotto/`, `app/api/saju/save-interpretation/`
|
||||||
|
- 라이브러리: `lib/ai-interpretation.ts`, `lib/daeun-calculator.ts`, `lib/saju-ai-prompt.ts`, `saju-engine/` (루트)
|
||||||
|
- 상품: `lib/products.ts` `saju_detail` (이관 또는 신규 도메인 재정의)
|
||||||
|
- DB: `subscriptions`/`orders`에 묶인 사주 결제 이력 — 결정 #5에 따라 이전/유지
|
||||||
|
|
||||||
|
**현 사이트 정리:**
|
||||||
|
- 제거: 위 라우트/API/lib (단, `lotto`가 사주와 공유되면 의존성 확인 — `app/api/saju/lotto` vs `app/api/saju/analyze`의 lib 공유 점검)
|
||||||
|
- 수정: `app/components/PublicShell.tsx` footer `AI 사주` 링크 → 외부 도메인 링크로 교체 또는 제거
|
||||||
|
- 수정: `app/page.tsx`(line 50·163·369), `app/work/page.tsx`(사주 카드), `app/mypage/page.tsx`(사주 참조), `app/sitemap.ts`, `app/layout.tsx`(사주 JSON-LD/keywords) — 사주 참조 제거 또는 외부 링크화
|
||||||
|
- 수정: `next.config.ts` — `/saju`·`/saju/input`·`/saju/result` 리다이렉트 3종을 **외부 도메인 301**로 변경(현재는 `/work/saju`로 내부 리다이렉트)
|
||||||
|
|
||||||
|
**작업 체크리스트:**
|
||||||
|
- [ ] 5-0. **도메인 구매 완료** (P5 착수 블로커 — 미완료 시 이하 진행 금지)
|
||||||
|
- [ ] 5-1. DB 호스팅 검토: Supabase 유지 vs NAS(Postgres) 이전 — 비용·가용성·RLS/Auth 대체·백업 평가 후 택1
|
||||||
|
- [ ] 5-2. 신규 도메인 프로젝트로 사주 라우트/API/lib/엔진 이관 + 동작 검증
|
||||||
|
- [ ] 5-3. 사주 결제·주문 이력 **전량** 신규 DB로 마이그레이션 + 건수 대조 검증
|
||||||
|
- [ ] 5-4. 현 사이트에서 사주 라우트/API/lib 제거 + 공유 의존성(예: lotto) 분리 확인
|
||||||
|
- [ ] 5-5. footer/홈/mypage/sitemap/layout 사주 참조를 외부 링크화 또는 제거
|
||||||
|
- [ ] 5-6. `next.config.ts` 사주 리다이렉트를 외부 도메인 301로 변경
|
||||||
|
- [ ] 5-7. 검증: 현 사이트 `npm run build` 성공 + 사주 잔존 참조 0건(외부 링크 제외)
|
||||||
|
- [ ] 5-8. 검증: 신규 도메인에서 사주 전 기능(입력→계산→AI해석→결제) 동작
|
||||||
|
- [ ] 5-9. 검증: `/saju*` 기존 URL이 신규 도메인으로 301
|
||||||
|
- [ ] 5-10. 커밋(현 사이트): `chore(saju): 사주 카탈로그 별도 도메인 분리 + 참조 제거`
|
||||||
|
|
||||||
|
**완료 조건:** 사주가 별도 도메인에서 독립 동작하고, 현 사이트에는 사주 코드가 남지 않으며, 기존 사주 URL이 신규 도메인으로 안전하게 연결됨.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 리다이렉트 정책 종합
|
||||||
|
|
||||||
|
| 기존 URL | 현재 동작 | 변경 후 (Phase) |
|
||||||
|
|---------|----------|----------------|
|
||||||
|
| `/services/blog` | → `/work/blog` (301) | → `/work` (301) **[P1]** |
|
||||||
|
| `/work/blog` | 페이지 존재 | 404 **[P1]** |
|
||||||
|
| `/services/music`, `/services/music/samples`, `/studio` | → `/music/*` | 유지 |
|
||||||
|
| `/freelance`, `/services/website*` | → `/work/*` | 유지 |
|
||||||
|
| `/saju`, `/saju/input`, `/saju/result` | → `/work/saju*` (내부) | → 외부 사주 도메인 (301) **[P5]** |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 롤백 전략
|
||||||
|
|
||||||
|
- 각 Phase = 독립 커밋(+ 가급적 PR). 문제 시 해당 커밋 revert로 단일 Phase만 되돌림.
|
||||||
|
- P1(블로그): 라우트 삭제는 git에서 복원 가능. 리다이렉트만 우선 배포해 트래픽 영향 관찰 후 코드 삭제하는 2단 배포도 가능.
|
||||||
|
- P4(음악 단품 디벨롭): 가격·타입 변경 없음(1회 결제 유지). 카피/메타 변경은 커밋 revert로 즉시 원복. 기존 구매자 0명이라 데이터 백업 불필요.
|
||||||
|
- P5(사주): 신규 도메인 안정화 **확인 후** 현 사이트 코드 삭제(삭제-먼저 금지). 이전 기간 동안 양쪽 병행 운영.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 실행 순서 요약
|
||||||
|
|
||||||
|
1. **지금** → P1(블로그 삭제) → P2(IA 라벨/카피 재편) → P4(음악을 "개발 가이드 패키지" 단품으로 디벨롭). 셋 다 위험 낮고 즉시 가능(P4는 구독 전환이 아니라 카피/메타 디벨롭이라 가벼움).
|
||||||
|
2. **메이킹 검증 자동화 1개 확정 후** → P3(SaaS 카탈로그). 음악 이탈로 첫 패키지 후보가 없어 그때까지 대기. 인프라는 보존.
|
||||||
|
3. **도메인 구매 후** → P5(사주 분리). 도메인 미구매가 단일 블로커. 가장 무겁고 외부 인프라 의존 → 마지막.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 변경 이력
|
||||||
|
|
||||||
|
- 2026-05-31: 초안 생성. 위키 `사업-쟁승메이드.md` 2026-05-29 재정의를 코드 마이그레이션 5-Phase로 분해. 현재 코드베이스 갭 분석 + 의사결정 보류 5항목 명시.
|
||||||
|
- 2026-05-31: 박재오 결정 5건 반영 — ①음악 구독 폐기→"AI 음악 생성 개발 가이드 패키지" 단품 디벨롭(P4 전면 재작성), ②기존 1회 구매자 0명 종결 기록, ③사주 분리 도메인 미구매로 P5 대기(블로커=도메인), ④사주 DB는 Supabase 유지 기본 + NAS 셀프호스팅 검토 편입, ⑤사주 결제 이력 전량 이전. 음악 이탈로 P3(SaaS 카탈로그) 첫 패키지 후보 공백 → P3 대기로 변경, 구독 인프라는 보존.
|
||||||
|
- 2026-05-31 (구현): **P1·P2·P4 실행 완료** + **P3 확장 골격 선구현**. 커밋 3건 — `chore(blog)`(블로그 완전 제거), `feat(ia)`(SaaS 카탈로그 `/packages` + 네비 3축 재편), `feat(music)`(음악 가이드 패키지 단품). 박재오 결정으로 P3 골격(`/packages` 경로 확정 + `lib/saas-catalog.ts` 확장 데이터 모델 + 빈 상태 예고/출시 알림 수집)을 먼저 구현 — 첫 SaaS 제품만 확정되면 배열 추가로 등판. 빌드 성공, 음악-구독 결합 흔적 0건. 브랜치 `feat/saas-pivot-migration`. P5(사주 분리)는 도메인 미구매로 미착수.
|
||||||
@@ -1,199 +0,0 @@
|
|||||||
import Anthropic from '@anthropic-ai/sdk';
|
|
||||||
import type { BlogRequest, BlogResult, BlogSection, ImageGuide } from './types';
|
|
||||||
|
|
||||||
let client: Anthropic | null = null;
|
|
||||||
|
|
||||||
function getClient(): Anthropic {
|
|
||||||
if (!client) {
|
|
||||||
const apiKey = process.env.ANTHROPIC_API_KEY;
|
|
||||||
if (!apiKey) throw new Error('ANTHROPIC_API_KEY not set');
|
|
||||||
client = new Anthropic({ apiKey });
|
|
||||||
}
|
|
||||||
return client;
|
|
||||||
}
|
|
||||||
|
|
||||||
const STYLE_LABELS: Record<string, string> = {
|
|
||||||
informational: '정보 전달형',
|
|
||||||
review: '리뷰/후기형',
|
|
||||||
howto: '방법/튜토리얼형',
|
|
||||||
listicle: '리스트형 (OO가지)',
|
|
||||||
comparison: '비교 분석형',
|
|
||||||
story: '에세이/스토리형',
|
|
||||||
};
|
|
||||||
|
|
||||||
const TONE_LABELS: Record<string, string> = {
|
|
||||||
professional: '전문적이고 신뢰감 있는',
|
|
||||||
friendly: '친근하고 대화하듯',
|
|
||||||
casual: '편한 말투, 구어체',
|
|
||||||
formal: '격식 있는 존댓말',
|
|
||||||
};
|
|
||||||
|
|
||||||
const LENGTH_RANGES: Record<string, string> = {
|
|
||||||
short: '800~1200자',
|
|
||||||
medium: '1500~2500자',
|
|
||||||
long: '3000~4500자',
|
|
||||||
};
|
|
||||||
|
|
||||||
function buildPrompt(req: BlogRequest): string {
|
|
||||||
return `당신은 네이버 블로그 SEO 전문 작가입니다.
|
|
||||||
|
|
||||||
## 작성 요청
|
|
||||||
|
|
||||||
- **주제**: ${req.topic}
|
|
||||||
- **핵심 키워드**: ${req.keywords.join(', ')}
|
|
||||||
- **글 형식**: ${STYLE_LABELS[req.style] || req.style}
|
|
||||||
- **톤앤매너**: ${TONE_LABELS[req.tone] || req.tone}
|
|
||||||
- **목표 분량**: ${LENGTH_RANGES[req.length] || req.length}
|
|
||||||
- **소제목 수**: ${req.sections}개
|
|
||||||
- **이미지 가이드**: ${req.imageGuide ? '포함' : '미포함'}
|
|
||||||
|
|
||||||
## 출력 형식 (반드시 아래 JSON 구조로 출력)
|
|
||||||
|
|
||||||
\`\`\`json
|
|
||||||
{
|
|
||||||
"title": "블로그 제목 (네이버 검색에 최적화된 30자 내외)",
|
|
||||||
"subtitle": "부제목 또는 한 줄 요약",
|
|
||||||
"sections": [
|
|
||||||
{
|
|
||||||
"heading": "소제목",
|
|
||||||
"body": "본문 내용 (마크다운 불릿·볼드 허용)",
|
|
||||||
"imageSlot": true
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"tags": ["태그1", "태그2", "태그3", "태그4", "태그5"],
|
|
||||||
"seoTitle": "검색 노출용 제목 (핵심 키워드 포함 40자 이내)",
|
|
||||||
"seoDescription": "메타 설명 (120자 이내, 키워드 자연스럽게 포함)",
|
|
||||||
"imageGuides": [
|
|
||||||
{
|
|
||||||
"position": "섹션 제목 또는 위치",
|
|
||||||
"description": "어떤 이미지가 적합한지 설명",
|
|
||||||
"searchKeyword": "이미지 검색 키워드",
|
|
||||||
"altText": "대체 텍스트"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
\`\`\`
|
|
||||||
|
|
||||||
## 작성 규칙
|
|
||||||
|
|
||||||
1. 네이버 블로그 검색 상위 노출을 위해 핵심 키워드를 제목·첫 문단·소제목에 자연스럽게 배치
|
|
||||||
2. 각 소제목(heading) 아래 본문은 구체적이고 실용적인 정보 포함
|
|
||||||
3. 첫 문단은 독자의 공감 또는 궁금증을 유발하는 도입부
|
|
||||||
4. 마지막 섹션은 요약 또는 CTA(댓글 유도, 공유 요청 등)
|
|
||||||
5. 본문에서 핵심 키워드는 전체 글의 2~3% 밀도로 자연스럽게 반복
|
|
||||||
6. imageSlot이 true인 섹션에는 반드시 imageGuides에 해당 위치의 이미지 가이드 포함
|
|
||||||
7. 태그는 5~8개, 관련 검색어와 롱테일 키워드 조합
|
|
||||||
8. JSON만 출력 — 설명이나 마크다운 코드블록 바깥 텍스트 금지`;
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function generateBlogPost(req: BlogRequest): Promise<BlogResult> {
|
|
||||||
const hasApiKey = !!process.env.ANTHROPIC_API_KEY;
|
|
||||||
|
|
||||||
if (hasApiKey) {
|
|
||||||
try {
|
|
||||||
const ai = getClient();
|
|
||||||
const response = await ai.messages.create({
|
|
||||||
model: 'claude-sonnet-4-20250514',
|
|
||||||
max_tokens: 4096,
|
|
||||||
messages: [
|
|
||||||
{ role: 'user', content: buildPrompt(req) },
|
|
||||||
],
|
|
||||||
});
|
|
||||||
|
|
||||||
const text = response.content
|
|
||||||
.filter((b): b is Anthropic.TextBlock => b.type === 'text')
|
|
||||||
.map((b) => b.text)
|
|
||||||
.join('');
|
|
||||||
|
|
||||||
// JSON 파싱 (코드블록 래핑 제거)
|
|
||||||
const jsonStr = text.replace(/^```json?\s*/i, '').replace(/\s*```$/i, '').trim();
|
|
||||||
const parsed = JSON.parse(jsonStr);
|
|
||||||
|
|
||||||
const sections: BlogSection[] = (parsed.sections || []).map((s: Record<string, unknown>) => ({
|
|
||||||
heading: String(s.heading || ''),
|
|
||||||
body: String(s.body || ''),
|
|
||||||
imageSlot: Boolean(s.imageSlot),
|
|
||||||
}));
|
|
||||||
|
|
||||||
const imageGuides: ImageGuide[] = (parsed.imageGuides || []).map((g: Record<string, unknown>) => ({
|
|
||||||
position: String(g.position || ''),
|
|
||||||
description: String(g.description || ''),
|
|
||||||
searchKeyword: String(g.searchKeyword || ''),
|
|
||||||
altText: String(g.altText || ''),
|
|
||||||
}));
|
|
||||||
|
|
||||||
const totalChars = sections.reduce((sum, s) => sum + s.heading.length + s.body.length, 0);
|
|
||||||
|
|
||||||
return {
|
|
||||||
success: true,
|
|
||||||
data: {
|
|
||||||
title: String(parsed.title || ''),
|
|
||||||
subtitle: String(parsed.subtitle || ''),
|
|
||||||
content: sections,
|
|
||||||
tags: Array.isArray(parsed.tags) ? parsed.tags.map(String) : [],
|
|
||||||
seoTitle: String(parsed.seoTitle || ''),
|
|
||||||
seoDescription: String(parsed.seoDescription || ''),
|
|
||||||
imageGuides,
|
|
||||||
meta: {
|
|
||||||
charCount: totalChars,
|
|
||||||
sectionCount: sections.length,
|
|
||||||
estimatedReadTime: `${Math.max(1, Math.round(totalChars / 500))}분`,
|
|
||||||
generatedAt: new Date().toISOString(),
|
|
||||||
model: 'claude-sonnet-4-20250514',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
} catch (err) {
|
|
||||||
console.error('[BlogGenerator] AI error, using fallback:', err);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fallback (API 키 없거나 실패 시)
|
|
||||||
return buildFallback(req);
|
|
||||||
}
|
|
||||||
|
|
||||||
function buildFallback(req: BlogRequest): BlogResult {
|
|
||||||
const sections: BlogSection[] = [];
|
|
||||||
for (let i = 0; i < req.sections; i++) {
|
|
||||||
if (i === 0) {
|
|
||||||
sections.push({
|
|
||||||
heading: `${req.topic}이란?`,
|
|
||||||
body: `${req.keywords[0] || req.topic}에 대해 알아보겠습니다. 이 글에서는 ${req.topic}의 핵심 내용을 정리해 드립니다.`,
|
|
||||||
imageSlot: req.imageGuide,
|
|
||||||
});
|
|
||||||
} else if (i === req.sections - 1) {
|
|
||||||
sections.push({
|
|
||||||
heading: '마치며',
|
|
||||||
body: `지금까지 ${req.topic}에 대해 알아보았습니다. 도움이 되셨다면 댓글과 공감 부탁드립니다!`,
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
sections.push({
|
|
||||||
heading: `포인트 ${i}`,
|
|
||||||
body: `${req.topic} 관련 상세 내용이 이 자리에 들어갑니다. AI API 키 설정 후 실제 콘텐츠가 생성됩니다.`,
|
|
||||||
imageSlot: i % 2 === 0 && req.imageGuide,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
success: true,
|
|
||||||
data: {
|
|
||||||
title: `${req.topic} — 완벽 가이드`,
|
|
||||||
subtitle: `${req.keywords.join(', ')} 핵심 정리`,
|
|
||||||
content: sections,
|
|
||||||
tags: req.keywords.slice(0, 5),
|
|
||||||
seoTitle: `${req.topic} ${req.keywords[0] || ''} 총정리`,
|
|
||||||
seoDescription: `${req.topic}에 대한 핵심 정보를 정리했습니다.`,
|
|
||||||
imageGuides: req.imageGuide
|
|
||||||
? [{ position: '도입부', description: '주제 대표 이미지', searchKeyword: req.keywords[0] || req.topic, altText: req.topic }]
|
|
||||||
: [],
|
|
||||||
meta: {
|
|
||||||
charCount: sections.reduce((s, sec) => s + sec.heading.length + sec.body.length, 0),
|
|
||||||
sectionCount: sections.length,
|
|
||||||
estimatedReadTime: '1분',
|
|
||||||
generatedAt: new Date().toISOString(),
|
|
||||||
model: 'fallback (no API key)',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -1,61 +0,0 @@
|
|||||||
export interface BlogRequest {
|
|
||||||
topic: string;
|
|
||||||
keywords: string[];
|
|
||||||
style: BlogStyle;
|
|
||||||
tone: BlogTone;
|
|
||||||
length: BlogLength;
|
|
||||||
imageGuide: boolean;
|
|
||||||
sections: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export type BlogStyle =
|
|
||||||
| 'informational' // 정보 전달
|
|
||||||
| 'review' // 리뷰/후기
|
|
||||||
| 'howto' // 방법/튜토리얼
|
|
||||||
| 'listicle' // 리스트형
|
|
||||||
| 'comparison' // 비교 분석
|
|
||||||
| 'story'; // 에세이/스토리
|
|
||||||
|
|
||||||
export type BlogTone =
|
|
||||||
| 'professional' // 전문적
|
|
||||||
| 'friendly' // 친근한
|
|
||||||
| 'casual' // 캐주얼
|
|
||||||
| 'formal'; // 격식체
|
|
||||||
|
|
||||||
export type BlogLength =
|
|
||||||
| 'short' // 800~1200자
|
|
||||||
| 'medium' // 1500~2500자
|
|
||||||
| 'long'; // 3000~4500자
|
|
||||||
|
|
||||||
export interface BlogResult {
|
|
||||||
success: true;
|
|
||||||
data: {
|
|
||||||
title: string;
|
|
||||||
subtitle: string;
|
|
||||||
content: BlogSection[];
|
|
||||||
tags: string[];
|
|
||||||
seoTitle: string;
|
|
||||||
seoDescription: string;
|
|
||||||
imageGuides: ImageGuide[];
|
|
||||||
meta: {
|
|
||||||
charCount: number;
|
|
||||||
sectionCount: number;
|
|
||||||
estimatedReadTime: string;
|
|
||||||
generatedAt: string;
|
|
||||||
model: string;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface BlogSection {
|
|
||||||
heading: string;
|
|
||||||
body: string;
|
|
||||||
imageSlot?: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ImageGuide {
|
|
||||||
position: string;
|
|
||||||
description: string;
|
|
||||||
searchKeyword: string;
|
|
||||||
altText: string;
|
|
||||||
}
|
|
||||||
64
lib/saas-catalog.ts
Normal file
64
lib/saas-catalog.ts
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
// SaaS 제품 카탈로그 (/packages)
|
||||||
|
//
|
||||||
|
// 확장 규칙: 새 SaaS 제품을 출시하면 SAAS_CATALOG 배열에 객체 하나만 추가하면
|
||||||
|
// /packages 페이지에 카드가 자동으로 노출된다. 결제는 productId로 lib/products.ts의
|
||||||
|
// PRODUCTS 정의와 subscriptions 인프라에 연결한다.
|
||||||
|
//
|
||||||
|
// 음악(AI 음악 생성 개발 가이드 패키지)은 단품 라인이므로 여기에 넣지 않는다(/music 유지).
|
||||||
|
|
||||||
|
export type SaasStatus = 'available' | 'coming_soon';
|
||||||
|
|
||||||
|
export interface SaasCatalogItem {
|
||||||
|
/** /packages 내 식별자 (향후 /packages/[slug] 상세에 사용) */
|
||||||
|
slug: string;
|
||||||
|
/** 카드 제목 */
|
||||||
|
name: string;
|
||||||
|
/** 한 줄 요약 (카드 상단) */
|
||||||
|
tagline: string;
|
||||||
|
/** 카드 본문 설명 */
|
||||||
|
description: string;
|
||||||
|
/** 가격 표시용 라벨 (예: "월 ₩29,000"). 미정이면 생략 */
|
||||||
|
priceLabel?: string;
|
||||||
|
/** 과금 형태 */
|
||||||
|
billing: 'monthly' | 'one_time';
|
||||||
|
/** 노출 상태 — available: 구매 가능 / coming_soon: 예고 */
|
||||||
|
status: SaasStatus;
|
||||||
|
/** 핵심 기능 목록 */
|
||||||
|
features: string[];
|
||||||
|
/** 분류 라벨 (예: '자동화') */
|
||||||
|
category: string;
|
||||||
|
/** lib/products.ts PRODUCTS 키 참조 (결제 연결, available일 때) */
|
||||||
|
productId?: string;
|
||||||
|
/** available일 때 상세/결제 경로 */
|
||||||
|
href?: string;
|
||||||
|
/** 카드 강조 뱃지 (예: 'NEW') */
|
||||||
|
badge?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 등록된 SaaS 제품 목록.
|
||||||
|
*
|
||||||
|
* 2026-05-31 현재 비어 있다. 메이킹 스페이스에서 검증된 자동화가 1개 확정되면
|
||||||
|
* 아래 형태로 항목을 추가한다:
|
||||||
|
*
|
||||||
|
* {
|
||||||
|
* slug: 'making-verify',
|
||||||
|
* name: '메이킹 검증 자동화',
|
||||||
|
* tagline: '...',
|
||||||
|
* description: '...',
|
||||||
|
* priceLabel: '월 ₩29,000',
|
||||||
|
* billing: 'monthly',
|
||||||
|
* status: 'available',
|
||||||
|
* features: ['...'],
|
||||||
|
* category: '자동화',
|
||||||
|
* productId: 'making_verify_monthly', // lib/products.ts에 함께 추가
|
||||||
|
* href: '/packages/making-verify',
|
||||||
|
* }
|
||||||
|
*/
|
||||||
|
export const SAAS_CATALOG: SaasCatalogItem[] = [];
|
||||||
|
|
||||||
|
export const getAvailablePackages = () =>
|
||||||
|
SAAS_CATALOG.filter((p) => p.status === 'available');
|
||||||
|
|
||||||
|
export const getComingSoonPackages = () =>
|
||||||
|
SAAS_CATALOG.filter((p) => p.status === 'coming_soon');
|
||||||
@@ -35,11 +35,13 @@ const nextConfig: NextConfig = {
|
|||||||
{ source: '/services/music', destination: '/music/packs', permanent: true },
|
{ source: '/services/music', destination: '/music/packs', permanent: true },
|
||||||
{ source: '/services/music/samples', destination: '/music/samples', permanent: true },
|
{ source: '/services/music/samples', destination: '/music/samples', permanent: true },
|
||||||
{ source: '/studio', destination: '/music/studio', permanent: true },
|
{ source: '/studio', destination: '/music/studio', permanent: true },
|
||||||
// Custom Build 사업부 마이그
|
// 커스텀 외주 마이그
|
||||||
{ source: '/freelance', destination: '/work/freelance', permanent: true },
|
{ source: '/freelance', destination: '/work/freelance', permanent: true },
|
||||||
{ source: '/services/website', destination: '/work/website', permanent: true },
|
{ source: '/services/website', destination: '/work/website', permanent: true },
|
||||||
{ source: '/services/website/samples/:slug', destination: '/work/website/samples/:slug', permanent: true },
|
{ source: '/services/website/samples/:slug', destination: '/work/website/samples/:slug', permanent: true },
|
||||||
{ source: '/services/blog', destination: '/work/blog', permanent: true },
|
// 블로그 자동화 폐기(2026-05-29 재정의): 기존 URL은 제품 라인 허브로 안내
|
||||||
|
{ source: '/services/blog', destination: '/work', permanent: true },
|
||||||
|
{ source: '/work/blog', destination: '/work', permanent: true },
|
||||||
// 사주 마이그 (단순 URL, 카탈로그 spec은 보류)
|
// 사주 마이그 (단순 URL, 카탈로그 spec은 보류)
|
||||||
{ source: '/saju', destination: '/work/saju', permanent: true },
|
{ source: '/saju', destination: '/work/saju', permanent: true },
|
||||||
{ source: '/saju/input', destination: '/work/saju/input', permanent: true },
|
{ source: '/saju/input', destination: '/work/saju/input', permanent: true },
|
||||||
|
|||||||
Reference in New Issue
Block a user