title: KoreaAI Hub 구현 플랜 (L2) date: 2026-02-26T21:00:00+09:00 type: plan layer: L2-implementation status: draft task_id: "" tags: [koreaai-hub, implementation, mvp, lemon-squeezy, brevo, next.js] author: koreaai-plan-pl project: koreaai-hub
KoreaAI Hub 구현 플랜
For Claude: REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
Goal: 한국 AI 비즈니스 허브 MVP를 2주 내 출시하여 뉴스레터 구독자 확보 + 스킬패키지 판매를 시작한다.
Architecture: Next.js 15 App Router 기반 풀스택 앱. Turso(LibSQL)에 별도 DB(koreaai-hub) 생성하여 구독자/상품/주문 관리. Lemon Squeezy로 결제 처리(Checkout URL + Webhook), Brevo로 뉴스레터 구독/발송. 랜딩 → 구독 → 결제 플로우를 MVP로 빠르게 구현.
Tech Stack:
- Next.js 15+ (App Router), TypeScript, Tailwind CSS v4, shadcn/ui
- Drizzle ORM + Turso (LibSQL) — DB명:
koreaai-hub - Lemon Squeezy (결제 PG, Merchant of Record)
- Brevo (@getbrevo/brevo, 뉴스레터/트랜잭션 이메일)
- Vercel (배포)
참고 문서:
- 비즈 기획(L0):
docs/plans/2026/02/25/koreaai-hub-research-v4.md - CEO 블로킹 해소:
memory/jarvis-reports/2026-02-26-1741-koreaai-ceo-unblock.md - 기존 프로젝트 참조:
projects/apppro-kr/current-site/(동일 기술 스택)
Phase 1 MVP 범위 (2주)
포함:
- 랜딩 페이지 (한국어, 반응형)
- 뉴스레터 구독 (이메일 수집 → Brevo 연동)
- AI 스킬패키지 상품 페이지 (5개 산업군)
- Lemon Squeezy 결제 연동 (Checkout → Webhook → 디지털 상품 전달)
- 관리자 대시보드 (구독자/주문 현황)
- SEO 기본 설정 (sitemap, robots, og:image)
- Vercel 배포
제외 (Phase 2 이후):
- BizFlow API
- 큐레이션 프리미엄 구독
- Featured Listing 광고
- AI 자동 콘텐츠 생성 파이프라인
- 개인화 필터
Task 1: 프로젝트 초기화 — Next.js + 의존성
Files:
- Create:
projects/koreaai-hub/package.json - Create:
projects/koreaai-hub/tsconfig.json - Create:
projects/koreaai-hub/next.config.ts - Create:
projects/koreaai-hub/tailwind.config.ts - Create:
projects/koreaai-hub/postcss.config.mjs - Create:
projects/koreaai-hub/.env.example - Create:
projects/koreaai-hub/.env.local - Create:
projects/koreaai-hub/.gitignore - Create:
projects/koreaai-hub/CLAUDE.md
Step 1: 프로젝트 디렉토리 생성 및 Next.js 초기화
cd /Users/nbs22/\(Claude\)/\(claude\).projects/business-builder/projects
mkdir -p koreaai-hub && cd koreaai-hub
npx create-next-app@latest . --typescript --tailwind --eslint --app --src-dir --import-alias "@/*" --use-pnpm --turbopack
Step 2: 추가 의존성 설치
cd /Users/nbs22/\(Claude\)/\(claude\).projects/business-builder/projects/koreaai-hub
pnpm add drizzle-orm @libsql/client @lemonsqueezy/lemonsqueezy.js @getbrevo/brevo
pnpm add class-variance-authority clsx tailwind-merge lucide-react framer-motion
pnpm add -D drizzle-kit @types/node
Step 3: shadcn/ui 초기화
cd /Users/nbs22/\(Claude\)/\(claude\).projects/business-builder/projects/koreaai-hub
pnpm dlx shadcn@latest init -d
Step 4: 환경변수 파일 생성
.env.example 파일:
# Turso DB
TURSO_DB_URL=libsql://koreaai-hub-migkjy.aws-ap-northeast-1.turso.io
TURSO_DB_TOKEN=
# Lemon Squeezy
LEMON_SQUEEZY_API_KEY=
LEMON_SQUEEZY_STORE_ID=
LEMON_SQUEEZY_WEBHOOK_SECRET=
# Brevo
BREVO_API_KEY=
BREVO_LIST_ID=
# App
NEXT_PUBLIC_APP_URL=http://localhost:3000
.env.local에 실제 키 복사 (루트 .env에서 가져옴).
Step 5: 프로젝트 CLAUDE.md 생성
# KoreaAI Hub
## 프로젝트 개요
한국 AI 비즈니스 허브 — 뉴스레터 + 스킬패키지 판매 플랫폼
## 기술 스택
- Next.js 15+ (App Router), TypeScript, Tailwind CSS v4, shadcn/ui
- Drizzle ORM + Turso (LibSQL) — DB: koreaai-hub
- Lemon Squeezy (결제), Brevo (이메일)
- 배포: Vercel
## GitHub Repo
- TBD (생성 후 기입)
## DB
- Turso DB명: koreaai-hub
- 환경변수: TURSO_DB_URL, TURSO_DB_TOKEN
Step 6: 빌드 확인
cd /Users/nbs22/\(Claude\)/\(claude\).projects/business-builder/projects/koreaai-hub
pnpm build
Expected: 빌드 성공 (exit 0)
Step 7: 커밋
cd /Users/nbs22/\(Claude\)/\(claude\).projects/business-builder
git add projects/koreaai-hub/
git commit -m "feat(koreaai-hub): 프로젝트 초기화 — Next.js 15 + Turso + Lemon Squeezy + Brevo"
git pull --rebase origin main && git push origin main
Task 2: Turso DB 생성 + Drizzle 스키마 정의
Files:
- Create:
projects/koreaai-hub/src/lib/db/index.ts - Create:
projects/koreaai-hub/src/lib/db/schema.ts - Create:
projects/koreaai-hub/drizzle.config.ts
Step 1: Turso DB 생성 (CLI)
turso db create koreaai-hub --group default
turso db tokens create koreaai-hub
생성된 URL과 토큰을 .env.local에 기입.
Step 2: Drizzle 스키마 정의
src/lib/db/schema.ts:
import { sql } from "drizzle-orm";
import { integer, sqliteTable, text, index, uniqueIndex } from "drizzle-orm/sqlite-core";
// 뉴스레터 구독자
export const subscribers = sqliteTable("subscribers", {
id: integer("id").primaryKey({ autoIncrement: true }),
email: text("email").notNull(),
name: text("name"),
tier: text("tier").$type<"free" | "starter" | "growth">().default("free").notNull(),
brevoContactId: text("brevo_contact_id"),
subscribedAt: text("subscribed_at").default(sql`(CURRENT_TIMESTAMP)`).notNull(),
unsubscribedAt: text("unsubscribed_at"),
status: text("status").$type<"active" | "unsubscribed" | "bounced">().default("active").notNull(),
}, (table) => [
uniqueIndex("subscribers_email_idx").on(table.email),
index("subscribers_status_idx").on(table.status),
]);
// 스킬패키지 상품
export const products = sqliteTable("products", {
id: integer("id").primaryKey({ autoIncrement: true }),
slug: text("slug").notNull(),
name: text("name").notNull(),
description: text("description"),
industry: text("industry").notNull(), // 마케팅, 금융, HR, 제조, 법률
price: integer("price").notNull(), // 센트 단위 (e.g., 1900 = $19)
currency: text("currency").default("USD").notNull(),
lsVariantId: text("ls_variant_id"), // Lemon Squeezy variant ID
lsProductId: text("ls_product_id"), // Lemon Squeezy product ID
type: text("type").$type<"single" | "bundle" | "subscription">().default("single").notNull(),
status: text("status").$type<"active" | "draft" | "archived">().default("draft").notNull(),
createdAt: text("created_at").default(sql`(CURRENT_TIMESTAMP)`).notNull(),
}, (table) => [
uniqueIndex("products_slug_idx").on(table.slug),
index("products_industry_idx").on(table.industry),
index("products_status_idx").on(table.status),
]);
// 주문
export const orders = sqliteTable("orders", {
id: integer("id").primaryKey({ autoIncrement: true }),
subscriberEmail: text("subscriber_email").notNull(),
productId: integer("product_id").references(() => products.id),
lsOrderId: text("ls_order_id"), // Lemon Squeezy order ID
status: text("status").$type<"pending" | "paid" | "refunded" | "failed">().default("pending").notNull(),
amount: integer("amount").notNull(), // 센트 단위
currency: text("currency").default("USD").notNull(),
downloadUrl: text("download_url"),
paidAt: text("paid_at"),
createdAt: text("created_at").default(sql`(CURRENT_TIMESTAMP)`).notNull(),
}, (table) => [
index("orders_email_idx").on(table.subscriberEmail),
index("orders_status_idx").on(table.status),
index("orders_ls_order_idx").on(table.lsOrderId),
]);
// 뉴스레터 발송 이력
export const newsletterIssues = sqliteTable("newsletter_issues", {
id: integer("id").primaryKey({ autoIncrement: true }),
subject: text("subject").notNull(),
htmlContent: text("html_content"),
brevoMessageId: text("brevo_message_id"),
sentAt: text("sent_at"),
recipientCount: integer("recipient_count").default(0),
openRate: integer("open_rate"), // 퍼센트 * 100 (e.g., 3500 = 35.00%)
status: text("status").$type<"draft" | "scheduled" | "sent" | "failed">().default("draft").notNull(),
createdAt: text("created_at").default(sql`(CURRENT_TIMESTAMP)`).notNull(),
}, (table) => [
index("newsletter_status_idx").on(table.status),
]);
// 타입 추출
export type InsertSubscriber = typeof subscribers.$inferInsert;
export type SelectSubscriber = typeof subscribers.$inferSelect;
export type InsertProduct = typeof products.$inferInsert;
export type SelectProduct = typeof products.$inferSelect;
export type InsertOrder = typeof orders.$inferInsert;
export type SelectOrder = typeof orders.$inferSelect;
Step 3: DB 클라이언트 설정
src/lib/db/index.ts:
import { drizzle } from "drizzle-orm/libsql";
import { createClient } from "@libsql/client";
import * as schema from "./schema";
const client = createClient({
url: process.env.TURSO_DB_URL!,
authToken: process.env.TURSO_DB_TOKEN,
});
export const db = drizzle(client, { schema });
Step 4: Drizzle config
drizzle.config.ts:
import type { Config } from "drizzle-kit";
export default {
schema: "./src/lib/db/schema.ts",
out: "./drizzle",
dialect: "turso",
dbCredentials: {
url: process.env.TURSO_DB_URL!,
authToken: process.env.TURSO_DB_TOKEN,
},
} satisfies Config;
Step 5: 마이그레이션 생성 및 확인
cd /Users/nbs22/\(Claude\)/\(claude\).projects/business-builder/projects/koreaai-hub
pnpm drizzle-kit generate
Expected: drizzle/ 폴더에 SQL 마이그레이션 파일 생성. SQL 내용 확인 — DROP/TRUNCATE가 없는지 반드시 검증.
Step 6: 마이그레이션 적용
cd /Users/nbs22/\(Claude\)/\(claude\).projects/business-builder/projects/koreaai-hub
pnpm drizzle-kit migrate
Expected: 테이블 4개 생성 (subscribers, products, orders, newsletter_issues)
Step 7: 빌드 확인
pnpm build
Expected: 빌드 성공
Step 8: 커밋
cd /Users/nbs22/\(Claude\)/\(claude\).projects/business-builder
git add projects/koreaai-hub/src/lib/db/ projects/koreaai-hub/drizzle.config.ts projects/koreaai-hub/drizzle/
git commit -m "feat(koreaai-hub): DB 스키마 정의 — subscribers, products, orders, newsletter_issues"
git pull --rebase origin main && git push origin main
Task 3: 유틸리티 + 공통 컴포넌트
Files:
- Create:
projects/koreaai-hub/src/lib/utils.ts - Create:
projects/koreaai-hub/src/components/ui/button.tsx(shadcn) - Create:
projects/koreaai-hub/src/components/ui/input.tsx(shadcn) - Create:
projects/koreaai-hub/src/components/ui/card.tsx(shadcn) - Create:
projects/koreaai-hub/src/components/ui/badge.tsx(shadcn)
Step 1: shadcn 컴포넌트 추가
cd /Users/nbs22/\(Claude\)/\(claude\).projects/business-builder/projects/koreaai-hub
pnpm dlx shadcn@latest add button input card badge separator toast
Step 2: utils.ts 생성
src/lib/utils.ts:
import { type ClassValue, clsx } from "clsx";
import { twMerge } from "tailwind-merge";
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
}
export function formatPrice(priceInCents: number, currency: string = "USD"): string {
return new Intl.NumberFormat("ko-KR", {
style: "currency",
currency,
}).format(priceInCents / 100);
}
export function formatDate(dateStr: string): string {
return new Intl.DateTimeFormat("ko-KR", {
year: "numeric",
month: "long",
day: "numeric",
}).format(new Date(dateStr));
}
Step 3: 빌드 확인
pnpm build
Step 4: 커밋
cd /Users/nbs22/\(Claude\)/\(claude\).projects/business-builder
git add projects/koreaai-hub/src/lib/utils.ts projects/koreaai-hub/src/components/
git commit -m "feat(koreaai-hub): 유틸리티 + shadcn UI 컴포넌트 추가"
git pull --rebase origin main && git push origin main
Task 4: Brevo 연동 — 뉴스레터 구독 API
Files:
- Create:
projects/koreaai-hub/src/lib/brevo.ts - Create:
projects/koreaai-hub/src/app/api/subscribe/route.ts - Create:
projects/koreaai-hub/src/__tests__/subscribe.test.ts
Step 1: Brevo 클라이언트 래퍼 작성
src/lib/brevo.ts:
import { ContactsApi, ContactsApiApiKeys, TransactionalEmailsApi, TransactionalEmailsApiApiKeys } from "@getbrevo/brevo";
function getContactsApi() {
const api = new ContactsApi();
api.setApiKey(ContactsApiApiKeys.apiKey, process.env.BREVO_API_KEY!);
return api;
}
function getEmailApi() {
const api = new TransactionalEmailsApi();
api.setApiKey(TransactionalEmailsApiApiKeys.apiKey, process.env.BREVO_API_KEY!);
return api;
}
export async function addSubscriber(email: string, name?: string) {
const api = getContactsApi();
const listId = parseInt(process.env.BREVO_LIST_ID || "0");
const result = await api.createContact({
email,
attributes: name ? { FIRSTNAME: name } : undefined,
listIds: listId ? [listId] : undefined,
updateEnabled: true,
});
return result.body;
}
export async function sendWelcomeEmail(email: string, name?: string) {
const api = getEmailApi();
await api.sendTransacEmail({
to: [{ email, name: name || undefined }],
subject: "KoreaAI Hub에 오신 것을 환영합니다!",
htmlContent: `
<h1>환영합니다${name ? `, ${name}` : ""}!</h1>
<p>한국 AI 비즈니스 인사이트를 매주 전달해드립니다.</p>
<p>곧 첫 번째 뉴스레터를 받아보실 수 있습니다.</p>
<hr/>
<p style="color:#666;font-size:12px;">KoreaAI Hub — 한국 AI 비즈니스 허브</p>
`,
sender: { email: "hello@koreaai.hub", name: "KoreaAI Hub" },
});
}
Step 2: 구독 API 라우트 작성
src/app/api/subscribe/route.ts:
import { NextRequest, NextResponse } from "next/server";
import { db } from "@/lib/db";
import { subscribers } from "@/lib/db/schema";
import { eq } from "drizzle-orm";
import { addSubscriber, sendWelcomeEmail } from "@/lib/brevo";
export async function POST(request: NextRequest) {
try {
const { email, name } = await request.json();
if (!email || typeof email !== "string" || !email.includes("@")) {
return NextResponse.json({ error: "유효한 이메일을 입력해주세요." }, { status: 400 });
}
const sanitizedEmail = email.trim().toLowerCase();
// 중복 확인
const existing = await db
.select({ id: subscribers.id, status: subscribers.status })
.from(subscribers)
.where(eq(subscribers.email, sanitizedEmail))
.limit(1);
if (existing.length > 0 && existing[0].status === "active") {
return NextResponse.json({ message: "이미 구독 중입니다." }, { status: 200 });
}
// DB 저장
if (existing.length > 0) {
await db.update(subscribers)
.set({ status: "active", unsubscribedAt: null, name: name || undefined })
.where(eq(subscribers.email, sanitizedEmail));
} else {
await db.insert(subscribers).values({
email: sanitizedEmail,
name: name || null,
tier: "free",
});
}
// Brevo 연동 (비동기, 실패해도 구독은 유지)
try {
await addSubscriber(sanitizedEmail, name);
await sendWelcomeEmail(sanitizedEmail, name);
} catch (brevoErr) {
console.error("Brevo 연동 실패:", brevoErr);
}
return NextResponse.json({ message: "구독 완료! 환영 이메일을 확인해주세요." }, { status: 201 });
} catch (error) {
console.error("구독 처리 실패:", error);
return NextResponse.json({ error: "서버 오류가 발생했습니다." }, { status: 500 });
}
}
Step 3: 빌드 확인
pnpm build
Step 4: 커밋
cd /Users/nbs22/\(Claude\)/\(claude\).projects/business-builder
git add projects/koreaai-hub/src/lib/brevo.ts projects/koreaai-hub/src/app/api/subscribe/
git commit -m "feat(koreaai-hub): Brevo 연동 + 뉴스레터 구독 API"
git pull --rebase origin main && git push origin main
Task 5: Lemon Squeezy 연동 — 결제 + Webhook
Files:
- Create:
projects/koreaai-hub/src/lib/lemonsqueezy.ts - Create:
projects/koreaai-hub/src/app/api/checkout/route.ts - Create:
projects/koreaai-hub/src/app/api/webhook/lemonsqueezy/route.ts
Step 1: Lemon Squeezy 클라이언트 설정
src/lib/lemonsqueezy.ts:
import {
lemonSqueezySetup,
createCheckout,
getProduct,
type Checkout,
} from "@lemonsqueezy/lemonsqueezy.js";
export function initLemonSqueezy() {
lemonSqueezySetup({
apiKey: process.env.LEMON_SQUEEZY_API_KEY!,
onError: (error) => console.error("Lemon Squeezy error:", error),
});
}
export async function createCheckoutUrl(
variantId: string,
email: string,
productName: string,
) {
initLemonSqueezy();
const storeId = process.env.LEMON_SQUEEZY_STORE_ID!;
const { data, error } = await createCheckout(storeId, variantId, {
checkoutData: {
email,
custom: {
product_name: productName,
},
},
productOptions: {
redirectUrl: `${process.env.NEXT_PUBLIC_APP_URL}/checkout/success`,
},
});
if (error) {
throw new Error(`Checkout 생성 실패: ${error.message}`);
}
return data?.data.attributes.url;
}
Step 2: 체크아웃 API 라우트
src/app/api/checkout/route.ts:
import { NextRequest, NextResponse } from "next/server";
import { db } from "@/lib/db";
import { products } from "@/lib/db/schema";
import { eq } from "drizzle-orm";
import { createCheckoutUrl } from "@/lib/lemonsqueezy";
export async function POST(request: NextRequest) {
try {
const { productSlug, email } = await request.json();
if (!productSlug || !email) {
return NextResponse.json({ error: "상품과 이메일이 필요합니다." }, { status: 400 });
}
const product = await db
.select()
.from(products)
.where(eq(products.slug, productSlug))
.limit(1);
if (!product.length || !product[0].lsVariantId) {
return NextResponse.json({ error: "상품을 찾을 수 없습니다." }, { status: 404 });
}
const checkoutUrl = await createCheckoutUrl(
product[0].lsVariantId,
email,
product[0].name,
);
return NextResponse.json({ checkoutUrl });
} catch (error) {
console.error("체크아웃 생성 실패:", error);
return NextResponse.json({ error: "서버 오류가 발생했습니다." }, { status: 500 });
}
}
Step 3: Webhook 핸들러
src/app/api/webhook/lemonsqueezy/route.ts:
import { NextRequest, NextResponse } from "next/server";
import crypto from "crypto";
import { db } from "@/lib/db";
import { orders } from "@/lib/db/schema";
import { eq } from "drizzle-orm";
function verifyWebhookSignature(
payload: string,
signature: string,
secret: string,
): boolean {
const hmac = crypto.createHmac("sha256", secret);
const digest = hmac.update(payload).digest("hex");
return crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(digest));
}
export async function POST(request: NextRequest) {
try {
const rawBody = await request.text();
const signature = request.headers.get("x-signature") || "";
const secret = process.env.LEMON_SQUEEZY_WEBHOOK_SECRET!;
if (!verifyWebhookSignature(rawBody, signature, secret)) {
return NextResponse.json({ error: "Invalid signature" }, { status: 401 });
}
const event = JSON.parse(rawBody);
const eventName = event.meta.event_name;
const data = event.data;
switch (eventName) {
case "order_created": {
const attrs = data.attributes;
await db.insert(orders).values({
subscriberEmail: attrs.user_email,
lsOrderId: String(data.id),
status: attrs.status === "paid" ? "paid" : "pending",
amount: attrs.total,
currency: attrs.currency.toUpperCase(),
paidAt: attrs.status === "paid" ? new Date().toISOString() : null,
});
break;
}
case "order_refunded": {
await db.update(orders)
.set({ status: "refunded" })
.where(eq(orders.lsOrderId, String(data.id)));
break;
}
default:
console.log(`Unhandled LS event: ${eventName}`);
}
return NextResponse.json({ received: true });
} catch (error) {
console.error("Webhook 처리 실패:", error);
return NextResponse.json({ error: "Webhook 처리 실패" }, { status: 500 });
}
}
Step 4: 빌드 확인
pnpm build
Step 5: 커밋
cd /Users/nbs22/\(Claude\)/\(claude\).projects/business-builder
git add projects/koreaai-hub/src/lib/lemonsqueezy.ts projects/koreaai-hub/src/app/api/checkout/ projects/koreaai-hub/src/app/api/webhook/
git commit -m "feat(koreaai-hub): Lemon Squeezy 결제 연동 — checkout + webhook"
git pull --rebase origin main && git push origin main
Task 6: 레이아웃 + 헤더 + 푸터
Files:
- Modify:
projects/koreaai-hub/src/app/layout.tsx - Create:
projects/koreaai-hub/src/components/layout/header.tsx - Create:
projects/koreaai-hub/src/components/layout/footer.tsx - Create:
projects/koreaai-hub/src/app/globals.css
Step 1: globals.css 설정 (Tailwind v4)
src/app/globals.css:
@import "tailwindcss";
:root {
--background: #ffffff;
--foreground: #0a0a0a;
--primary: #2563eb;
--primary-foreground: #ffffff;
--secondary: #06b6d4;
--muted: #f5f5f5;
--muted-foreground: #737373;
--border: #e5e5e5;
--accent: #f0f9ff;
}
@media (prefers-color-scheme: dark) {
:root {
--background: #0a0a0a;
--foreground: #ededed;
--primary: #3b82f6;
--muted: #1a1a1a;
--muted-foreground: #a3a3a3;
--border: #262626;
--accent: #0c1929;
}
}
body {
font-family: "Pretendard Variable", Pretendard, -apple-system, BlinkMacSystemFont,
system-ui, Roboto, "Helvetica Neue", "Segoe UI", "Apple SD Gothic Neo",
"Noto Sans KR", "Malgun Gothic", "Apple Color Emoji", "Segoe UI Emoji",
"Segoe UI Symbol", sans-serif;
}
Step 2: Header 컴포넌트
src/components/layout/header.tsx:
import Link from "next/link";
export function Header() {
return (
<header className="sticky top-0 z-50 w-full border-b border-[var(--border)] bg-[var(--background)]/80 backdrop-blur-sm">
<div className="mx-auto flex h-16 max-w-6xl items-center justify-between px-4">
<Link href="/" className="text-xl font-bold text-[var(--primary)]">
KoreaAI Hub
</Link>
<nav className="flex items-center gap-6">
<Link href="/skills" className="text-sm text-[var(--muted-foreground)] hover:text-[var(--foreground)] transition-colors">
스킬패키지
</Link>
<Link href="/newsletter" className="text-sm text-[var(--muted-foreground)] hover:text-[var(--foreground)] transition-colors">
뉴스레터
</Link>
<Link
href="/newsletter"
className="rounded-lg bg-[var(--primary)] px-4 py-2 text-sm font-medium text-[var(--primary-foreground)] hover:opacity-90 transition-opacity"
>
무료 구독
</Link>
</nav>
</div>
</header>
);
}
Step 3: Footer 컴포넌트
src/components/layout/footer.tsx:
export function Footer() {
return (
<footer className="border-t border-[var(--border)] bg-[var(--muted)]">
<div className="mx-auto max-w-6xl px-4 py-12">
<div className="grid grid-cols-1 gap-8 md:grid-cols-3">
<div>
<h3 className="text-lg font-bold">KoreaAI Hub</h3>
<p className="mt-2 text-sm text-[var(--muted-foreground)]">
한국 AI 비즈니스의 모든 것
</p>
</div>
<div>
<h4 className="font-semibold">상품</h4>
<ul className="mt-2 space-y-1 text-sm text-[var(--muted-foreground)]">
<li>AI 스킬패키지</li>
<li>비즈 아이디어 뉴스레터</li>
<li>큐레이션 프리미엄 (곧 출시)</li>
</ul>
</div>
<div>
<h4 className="font-semibold">문의</h4>
<p className="mt-2 text-sm text-[var(--muted-foreground)]">
hello@koreaai.hub
</p>
</div>
</div>
<div className="mt-8 border-t border-[var(--border)] pt-4 text-center text-xs text-[var(--muted-foreground)]">
© {new Date().getFullYear()} KoreaAI Hub. All rights reserved.
</div>
</div>
</footer>
);
}
Step 4: layout.tsx 수정
src/app/layout.tsx:
import type { Metadata } from "next";
import { Header } from "@/components/layout/header";
import { Footer } from "@/components/layout/footer";
import "./globals.css";
export const metadata: Metadata = {
title: "KoreaAI Hub — 한국 AI 비즈니스 허브",
description: "한국 산업에 특화된 AI 스킬패키지, 비즈 아이디어 뉴스레터, 큐레이션 서비스를 제공합니다.",
openGraph: {
title: "KoreaAI Hub — 한국 AI 비즈니스 허브",
description: "한국 산업에 특화된 AI 스킬패키지와 비즈 아이디어 뉴스레터",
type: "website",
},
};
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html lang="ko">
<head>
<link
rel="stylesheet"
as="style"
crossOrigin="anonymous"
href="https://cdn.jsdelivr.net/gh/orioncactus/pretendard@v1.3.9/dist/web/variable/pretendardvariable-dynamic-subset.min.css"
/>
</head>
<body className="bg-[var(--background)] text-[var(--foreground)]">
<Header />
<main className="min-h-screen">{children}</main>
<Footer />
</body>
</html>
);
}
Step 5: 빌드 확인
pnpm build
Step 6: 커밋
cd /Users/nbs22/\(Claude\)/\(claude\).projects/business-builder
git add projects/koreaai-hub/src/app/layout.tsx projects/koreaai-hub/src/app/globals.css projects/koreaai-hub/src/components/layout/
git commit -m "feat(koreaai-hub): 레이아웃 + 헤더 + 푸터 — Pretendard 폰트, 다크모드"
git pull --rebase origin main && git push origin main
Task 7: 랜딩 페이지 — 메인
Files:
- Modify:
projects/koreaai-hub/src/app/page.tsx - Create:
projects/koreaai-hub/src/components/sections/hero-section.tsx - Create:
projects/koreaai-hub/src/components/sections/features-section.tsx - Create:
projects/koreaai-hub/src/components/sections/newsletter-cta.tsx - Create:
projects/koreaai-hub/src/components/sections/skills-preview-section.tsx
Step 1: Hero 섹션
src/components/sections/hero-section.tsx:
import Link from "next/link";
export function HeroSection() {
return (
<section className="relative overflow-hidden py-24 md:py-32">
<div className="mx-auto max-w-6xl px-4 text-center">
<div className="inline-block rounded-full bg-[var(--accent)] px-4 py-1.5 text-sm font-medium text-[var(--primary)] mb-6">
한국 AI 비즈니스 특화
</div>
<h1 className="text-4xl font-bold tracking-tight md:text-6xl">
AI로 비즈니스를 시작하는
<br />
<span className="text-[var(--primary)]">가장 빠른 방법</span>
</h1>
<p className="mx-auto mt-6 max-w-2xl text-lg text-[var(--muted-foreground)]">
한국 산업에 딱 맞는 AI 스킬패키지와 매일 실행 가능한 비즈 아이디어를
뉴스레터로 받아보세요.
</p>
<div className="mt-10 flex flex-col items-center gap-4 sm:flex-row sm:justify-center">
<Link
href="/newsletter"
className="rounded-lg bg-[var(--primary)] px-8 py-3 text-base font-semibold text-[var(--primary-foreground)] hover:opacity-90 transition-opacity"
>
무료 뉴스레터 구독
</Link>
<Link
href="/skills"
className="rounded-lg border border-[var(--border)] px-8 py-3 text-base font-semibold hover:bg-[var(--muted)] transition-colors"
>
스킬패키지 보기
</Link>
</div>
</div>
</section>
);
}
Step 2: Features 섹션
src/components/sections/features-section.tsx:
const features = [
{
title: "산업군별 AI 스킬",
description: "마케팅, 금융, HR, 제조, 법률 — 내 업종에 딱 맞는 AI 활용법 패키지",
icon: "🎯",
},
{
title: "실행 가능한 비즈 아이디어",
description: "매일 '오늘 당장 시작 가능한' AI 비즈니스 아이디어를 뉴스레터로 배달",
icon: "💡",
},
{
title: "한국 시장 특화",
description: "한국 법률, 세금, 시장 환경에 최적화된 AI 활용 전략과 프롬프트",
icon: "🇰🇷",
},
];
export function FeaturesSection() {
return (
<section className="border-t border-[var(--border)] bg-[var(--muted)] py-20">
<div className="mx-auto max-w-6xl px-4">
<h2 className="text-center text-3xl font-bold">왜 KoreaAI Hub인가</h2>
<div className="mt-12 grid grid-cols-1 gap-8 md:grid-cols-3">
{features.map((f) => (
<div
key={f.title}
className="rounded-xl border border-[var(--border)] bg-[var(--background)] p-6"
>
<div className="text-4xl">{f.icon}</div>
<h3 className="mt-4 text-xl font-semibold">{f.title}</h3>
<p className="mt-2 text-[var(--muted-foreground)]">{f.description}</p>
</div>
))}
</div>
</div>
</section>
);
}
Step 3: Newsletter CTA 섹션
src/components/sections/newsletter-cta.tsx:
"use client";
import { useState } from "react";
export function NewsletterCta() {
const [email, setEmail] = useState("");
const [status, setStatus] = useState<"idle" | "loading" | "success" | "error">("idle");
const [message, setMessage] = useState("");
async function handleSubmit(e: React.FormEvent) {
e.preventDefault();
setStatus("loading");
try {
const res = await fetch("/api/subscribe", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ email }),
});
const data = await res.json();
if (res.ok) {
setStatus("success");
setMessage(data.message);
setEmail("");
} else {
setStatus("error");
setMessage(data.error || "오류가 발생했습니다.");
}
} catch {
setStatus("error");
setMessage("네트워크 오류가 발생했습니다.");
}
}
return (
<section className="py-20">
<div className="mx-auto max-w-2xl px-4 text-center">
<h2 className="text-3xl font-bold">
매주 AI 비즈 아이디어를 받아보세요
</h2>
<p className="mt-4 text-[var(--muted-foreground)]">
무료 구독으로 시작하세요. 주 3회 AI 비즈니스 인사이트를 보내드립니다.
</p>
<form onSubmit={handleSubmit} className="mt-8 flex gap-2 sm:flex-row flex-col">
<input
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
placeholder="your@email.com"
required
className="flex-1 rounded-lg border border-[var(--border)] bg-[var(--background)] px-4 py-3 text-base focus:outline-none focus:ring-2 focus:ring-[var(--primary)]"
/>
<button
type="submit"
disabled={status === "loading"}
className="rounded-lg bg-[var(--primary)] px-6 py-3 font-semibold text-[var(--primary-foreground)] hover:opacity-90 transition-opacity disabled:opacity-50"
>
{status === "loading" ? "처리 중..." : "무료 구독"}
</button>
</form>
{message && (
<p className={`mt-4 text-sm ${status === "success" ? "text-green-600" : "text-red-600"}`}>
{message}
</p>
)}
</div>
</section>
);
}
Step 4: Skills Preview 섹션
src/components/sections/skills-preview-section.tsx:
import Link from "next/link";
const industries = [
{ name: "마케팅/광고", slug: "marketing", emoji: "📢", desc: "AI 기반 광고 카피, 타겟팅, 캠페인 자동화" },
{ name: "금융/핀테크", slug: "finance", emoji: "💰", desc: "AI 리스크 분석, 고객 신용평가, 자동 보고서" },
{ name: "HR/채용", slug: "hr", emoji: "👥", desc: "AI 이력서 스크리닝, 면접 질문 생성, 노무 상담" },
{ name: "제조업", slug: "manufacturing", emoji: "🏭", desc: "AI 품질검사, 공정 최적화, 예측 정비" },
{ name: "법률/컨설팅", slug: "legal", emoji: "⚖️", desc: "AI 계약서 검토, 법률 리서치, 사례 분석" },
];
export function SkillsPreviewSection() {
return (
<section className="border-t border-[var(--border)] py-20">
<div className="mx-auto max-w-6xl px-4">
<div className="text-center">
<h2 className="text-3xl font-bold">산업군별 AI 스킬패키지</h2>
<p className="mt-4 text-[var(--muted-foreground)]">
내 업종에 딱 맞는 AI 활용법. 프롬프트 10종 + 실전 가이드 포함.
</p>
</div>
<div className="mt-12 grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3">
{industries.map((ind) => (
<Link
key={ind.slug}
href={`/skills/${ind.slug}`}
className="flex items-start gap-4 rounded-xl border border-[var(--border)] p-5 hover:border-[var(--primary)] hover:bg-[var(--accent)] transition-colors"
>
<span className="text-3xl">{ind.emoji}</span>
<div>
<h3 className="font-semibold">{ind.name}</h3>
<p className="mt-1 text-sm text-[var(--muted-foreground)]">{ind.desc}</p>
<span className="mt-2 inline-block text-sm font-medium text-[var(--primary)]">
$19 →
</span>
</div>
</Link>
))}
</div>
<div className="mt-8 text-center">
<Link
href="/skills"
className="inline-block rounded-lg border border-[var(--border)] px-6 py-3 font-medium hover:bg-[var(--muted)] transition-colors"
>
전체 패키지 보기
</Link>
</div>
</div>
</section>
);
}
Step 5: 메인 페이지 조합
src/app/page.tsx:
import { HeroSection } from "@/components/sections/hero-section";
import { FeaturesSection } from "@/components/sections/features-section";
import { SkillsPreviewSection } from "@/components/sections/skills-preview-section";
import { NewsletterCta } from "@/components/sections/newsletter-cta";
export default function HomePage() {
return (
<>
<HeroSection />
<FeaturesSection />
<SkillsPreviewSection />
<NewsletterCta />
</>
);
}
Step 6: 빌드 확인
pnpm build
Step 7: 커밋
cd /Users/nbs22/\(Claude\)/\(claude\).projects/business-builder
git add projects/koreaai-hub/src/app/page.tsx projects/koreaai-hub/src/components/sections/
git commit -m "feat(koreaai-hub): 랜딩 페이지 — hero, features, skills preview, newsletter CTA"
git pull --rebase origin main && git push origin main
Task 8: 뉴스레터 구독 전용 페이지
Files:
- Create:
projects/koreaai-hub/src/app/newsletter/page.tsx
Step 1: 뉴스레터 페이지
src/app/newsletter/page.tsx:
import type { Metadata } from "next";
import { NewsletterCta } from "@/components/sections/newsletter-cta";
export const metadata: Metadata = {
title: "AI 비즈 아이디어 뉴스레터 — KoreaAI Hub",
description: "매주 3회, 한국에서 당장 실행 가능한 AI 비즈니스 아이디어를 배달합니다.",
};
const benefits = [
{ title: "주 3회 무료 배달", desc: "월/수/금 아침, 실행 가능한 AI 비즈 아이디어 1건" },
{ title: "한국 시장 특화", desc: "해외 트렌드를 한국 법/세금/시장에 맞게 분석" },
{ title: "실행 가이드 포함", desc: "아이디어 + 초기비용 + 수익예상 + 3일 실행플랜" },
{ title: "Pro 업그레이드", desc: "$4.99/월로 일간 즉시 수신 + 아카이브 검색" },
];
export default function NewsletterPage() {
return (
<div className="py-16">
<div className="mx-auto max-w-4xl px-4">
<div className="text-center">
<h1 className="text-4xl font-bold">
매일 아침, AI 비즈 아이디어가 도착합니다
</h1>
<p className="mt-4 text-lg text-[var(--muted-foreground)]">
"오늘 당장 실행 가능한 AI 비즈니스 아이디어 1개" — 리서치, 전략, 실행 방법까지
</p>
</div>
<div className="mt-12 grid grid-cols-1 gap-6 sm:grid-cols-2">
{benefits.map((b) => (
<div key={b.title} className="rounded-xl border border-[var(--border)] p-6">
<h3 className="font-semibold text-lg">{b.title}</h3>
<p className="mt-2 text-sm text-[var(--muted-foreground)]">{b.desc}</p>
</div>
))}
</div>
<div className="mt-12 rounded-xl border border-[var(--border)] bg-[var(--muted)] p-8">
<h2 className="text-center text-2xl font-bold mb-2">뉴스레터 미리보기</h2>
<div className="mx-auto max-w-lg rounded-lg bg-[var(--background)] p-6 border border-[var(--border)]">
<div className="text-xs text-[var(--muted-foreground)] mb-2">2026년 2월 26일 (수)</div>
<h3 className="text-lg font-bold">AI 기반 세무 자동화 대행 서비스</h3>
<p className="mt-2 text-sm text-[var(--muted-foreground)]">
한국 세무사 수요 급증 + AI 세무 자동화 기술 성숙 = 소상공인 대상 월 $29 구독 서비스.
초기 비용 $50, 예상 월 수익 $2,000+
</p>
<div className="mt-3 text-xs text-[var(--primary)] font-medium">
전체 실행 가이드는 구독 후 확인 →
</div>
</div>
</div>
<NewsletterCta />
</div>
</div>
);
}
Step 2: 빌드 확인
pnpm build
Step 3: 커밋
cd /Users/nbs22/\(Claude\)/\(claude\).projects/business-builder
git add projects/koreaai-hub/src/app/newsletter/
git commit -m "feat(koreaai-hub): 뉴스레터 구독 전용 페이지"
git pull --rebase origin main && git push origin main
Task 9: 스킬패키지 목록 페이지
Files:
- Create:
projects/koreaai-hub/src/app/skills/page.tsx - Create:
projects/koreaai-hub/src/lib/data/industries.ts
Step 1: 산업군 데이터
src/lib/data/industries.ts:
export interface Industry {
slug: string;
name: string;
emoji: string;
description: string;
skills: string[];
price: number; // 센트
}
export const industries: Industry[] = [
{
slug: "marketing",
name: "마케팅/광고",
emoji: "📢",
description: "AI 기반 광고 카피라이팅, 타겟 고객 분석, 캠페인 자동화 등 마케팅 실무에 즉시 적용 가능한 AI 스킬",
skills: [
"AI 광고 카피 생성 (네이버/카카오 맞춤)",
"타겟 페르소나 자동 분석",
"SNS 콘텐츠 캘린더 자동 생성",
"A/B 테스트 카피 변형 생성",
"경쟁사 광고 분석 프롬프트",
"이메일 마케팅 시퀀스 설계",
"블로그 SEO 키워드 리서치",
"랜딩페이지 카피 최적화",
"고객 리뷰 분석 및 인사이트 추출",
"마케팅 보고서 자동 생성",
],
price: 1900,
},
{
slug: "finance",
name: "금융/핀테크",
emoji: "💰",
description: "AI 리스크 분석, 신용평가 자동화, 금융 보고서 생성 등 한국 금융 규제에 맞춘 AI 활용법",
skills: [
"재무제표 AI 분석 프롬프트",
"신용 리스크 평가 자동화",
"투자 포트폴리오 분석",
"한국 세법 기반 절세 전략 AI",
"금융 규제 컴플라이언스 체크",
"고객 이탈 예측 모델 설계",
"자동 투자 보고서 생성",
"사기 탐지 패턴 분석",
"대출 심사 자동화 워크플로우",
"금융 뉴스 감성 분석",
],
price: 1900,
},
{
slug: "hr",
name: "HR/채용",
emoji: "👥",
description: "AI 이력서 스크리닝, 면접 질문 자동 생성, 한국 노동법 기반 노무 상담 등 HR 전문 스킬",
skills: [
"이력서/자기소개서 AI 스크리닝",
"직무별 면접 질문 자동 생성",
"한국 노동법 기반 노무 상담 AI",
"연봉 벤치마킹 분석",
"직원 만족도 설문 분석",
"온보딩 체크리스트 자동 생성",
"퇴사 예측 분석 프롬프트",
"채용 공고 최적화",
"교육 프로그램 커리큘럼 설계",
"인사 평가 피드백 작성 지원",
],
price: 1900,
},
{
slug: "manufacturing",
name: "제조업",
emoji: "🏭",
description: "AI 품질검사, 공정 최적화, 예측 정비 등 한국 제조업 현장에 맞춘 AI 도입 가이드",
skills: [
"품질검사 이미지 분석 워크플로우",
"공정 최적화 데이터 분석",
"예측 정비 스케줄 자동화",
"재고 수요 예측 모델",
"공급망 리스크 분석",
"제조 원가 분석 자동화",
"안전사고 예방 체크리스트 AI",
"생산 보고서 자동 생성",
"장비 이상 탐지 패턴 분석",
"스마트팩토리 도입 ROI 분석",
],
price: 1900,
},
{
slug: "legal",
name: "법률/컨설팅",
emoji: "⚖️",
description: "AI 계약서 검토, 법률 리서치, 사례 분석 등 한국 법체계에 특화된 AI 활용 패키지",
skills: [
"계약서 AI 검토 프롬프트",
"판례 검색 및 요약 자동화",
"법률 의견서 초안 생성",
"컴플라이언스 체크리스트 자동화",
"특허 검색 및 선행기술 분석",
"법률 용어 쉬운 한글 변환",
"소송 리스크 평가 분석",
"정관/약관 AI 검토",
"법률 상담 초기 응대 자동화",
"규제 변경 모니터링 설정",
],
price: 1900,
},
];
export const bundlePrice = 4900; // $49 = All-in-One
Step 2: 스킬패키지 목록 페이지
src/app/skills/page.tsx:
import type { Metadata } from "next";
import Link from "next/link";
import { industries, bundlePrice } from "@/lib/data/industries";
import { formatPrice } from "@/lib/utils";
export const metadata: Metadata = {
title: "AI 스킬패키지 — KoreaAI Hub",
description: "한국 산업군별 AI 활용 스킬패키지. 프롬프트 10종 + 실전 가이드.",
};
export default function SkillsPage() {
return (
<div className="py-16">
<div className="mx-auto max-w-6xl px-4">
<div className="text-center">
<h1 className="text-4xl font-bold">산업군별 AI 스킬패키지</h1>
<p className="mt-4 text-lg text-[var(--muted-foreground)]">
내 업종에 딱 맞는 AI 활용법 10가지 + 실전 가이드. 한국 시장 특화.
</p>
</div>
{/* 번들 배너 */}
<div className="mt-12 rounded-xl border-2 border-[var(--primary)] bg-[var(--accent)] p-6 text-center">
<div className="text-sm font-medium text-[var(--primary)]">BEST VALUE</div>
<h2 className="mt-2 text-2xl font-bold">All-in-One 패키지</h2>
<p className="mt-2 text-[var(--muted-foreground)]">
전체 {industries.length}개 산업군 × 10개 스킬 = 총 {industries.length * 10}개 AI 스킬
</p>
<div className="mt-4 flex items-center justify-center gap-3">
<span className="text-2xl font-bold text-[var(--primary)]">
{formatPrice(bundlePrice)}
</span>
<span className="text-sm text-[var(--muted-foreground)] line-through">
{formatPrice(industries.length * 1900)}
</span>
<span className="rounded-full bg-green-100 px-2 py-0.5 text-xs font-medium text-green-700">
{Math.round((1 - bundlePrice / (industries.length * 1900)) * 100)}% 할인
</span>
</div>
<Link
href="/skills/bundle"
className="mt-4 inline-block rounded-lg bg-[var(--primary)] px-8 py-3 font-semibold text-[var(--primary-foreground)] hover:opacity-90 transition-opacity"
>
번들 구매하기
</Link>
</div>
{/* 개별 산업군 카드 */}
<div className="mt-12 grid grid-cols-1 gap-6 md:grid-cols-2 lg:grid-cols-3">
{industries.map((ind) => (
<Link
key={ind.slug}
href={`/skills/${ind.slug}`}
className="group rounded-xl border border-[var(--border)] p-6 hover:border-[var(--primary)] hover:shadow-md transition-all"
>
<div className="text-4xl">{ind.emoji}</div>
<h3 className="mt-3 text-xl font-semibold group-hover:text-[var(--primary)]">
{ind.name}
</h3>
<p className="mt-2 text-sm text-[var(--muted-foreground)] line-clamp-2">
{ind.description}
</p>
<div className="mt-4 flex items-center justify-between">
<span className="text-lg font-bold">{formatPrice(ind.price)}</span>
<span className="text-sm text-[var(--muted-foreground)]">
프롬프트 {ind.skills.length}종
</span>
</div>
</Link>
))}
</div>
</div>
</div>
);
}
Step 3: 빌드 확인
pnpm build
Step 4: 커밋
cd /Users/nbs22/\(Claude\)/\(claude\).projects/business-builder
git add projects/koreaai-hub/src/app/skills/ projects/koreaai-hub/src/lib/data/
git commit -m "feat(koreaai-hub): 스킬패키지 목록 페이지 + 산업군 데이터"
git pull --rebase origin main && git push origin main
Task 10: 스킬패키지 상세 페이지 + 결제 버튼
Files:
- Create:
projects/koreaai-hub/src/app/skills/[slug]/page.tsx - Create:
projects/koreaai-hub/src/components/checkout-button.tsx
Step 1: 체크아웃 버튼 컴포넌트
src/components/checkout-button.tsx:
"use client";
import { useState } from "react";
interface CheckoutButtonProps {
productSlug: string;
label?: string;
className?: string;
}
export function CheckoutButton({ productSlug, label = "구매하기", className = "" }: CheckoutButtonProps) {
const [email, setEmail] = useState("");
const [loading, setLoading] = useState(false);
const [showEmailInput, setShowEmailInput] = useState(false);
async function handleCheckout() {
if (!showEmailInput) {
setShowEmailInput(true);
return;
}
if (!email || !email.includes("@")) return;
setLoading(true);
try {
const res = await fetch("/api/checkout", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ productSlug, email }),
});
const data = await res.json();
if (data.checkoutUrl) {
window.location.href = data.checkoutUrl;
}
} catch {
alert("결제 페이지 연결에 실패했습니다. 다시 시도해주세요.");
} finally {
setLoading(false);
}
}
return (
<div className={className}>
{showEmailInput && (
<input
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
placeholder="결제용 이메일 주소"
className="mb-3 w-full rounded-lg border border-[var(--border)] px-4 py-3 text-base focus:outline-none focus:ring-2 focus:ring-[var(--primary)]"
/>
)}
<button
onClick={handleCheckout}
disabled={loading}
className="w-full rounded-lg bg-[var(--primary)] px-8 py-3 font-semibold text-[var(--primary-foreground)] hover:opacity-90 transition-opacity disabled:opacity-50"
>
{loading ? "처리 중..." : label}
</button>
</div>
);
}
Step 2: 스킬패키지 상세 페이지
src/app/skills/[slug]/page.tsx:
import type { Metadata } from "next";
import { notFound } from "next/navigation";
import { industries } from "@/lib/data/industries";
import { formatPrice } from "@/lib/utils";
import { CheckoutButton } from "@/components/checkout-button";
import { NewsletterCta } from "@/components/sections/newsletter-cta";
interface PageProps {
params: Promise<{ slug: string }>;
}
export async function generateStaticParams() {
return industries.map((ind) => ({ slug: ind.slug }));
}
export async function generateMetadata({ params }: PageProps): Promise<Metadata> {
const { slug } = await params;
const industry = industries.find((i) => i.slug === slug);
if (!industry) return {};
return {
title: `${industry.name} AI 스킬패키지 — KoreaAI Hub`,
description: industry.description,
};
}
export default async function SkillDetailPage({ params }: PageProps) {
const { slug } = await params;
const industry = industries.find((i) => i.slug === slug);
if (!industry) notFound();
return (
<div className="py-16">
<div className="mx-auto max-w-4xl px-4">
<div className="flex items-start gap-4">
<span className="text-5xl">{industry.emoji}</span>
<div>
<h1 className="text-3xl font-bold">{industry.name} AI 스킬패키지</h1>
<p className="mt-2 text-[var(--muted-foreground)]">{industry.description}</p>
</div>
</div>
<div className="mt-12 grid grid-cols-1 gap-8 lg:grid-cols-3">
{/* 스킬 목록 */}
<div className="lg:col-span-2">
<h2 className="text-xl font-bold">포함된 AI 스킬 {industry.skills.length}종</h2>
<ul className="mt-4 space-y-3">
{industry.skills.map((skill, idx) => (
<li key={idx} className="flex items-start gap-3 rounded-lg border border-[var(--border)] p-4">
<span className="flex h-6 w-6 shrink-0 items-center justify-center rounded-full bg-[var(--primary)] text-xs font-bold text-white">
{idx + 1}
</span>
<span>{skill}</span>
</li>
))}
</ul>
</div>
{/* 구매 카드 */}
<div className="lg:col-span-1">
<div className="sticky top-24 rounded-xl border border-[var(--border)] p-6">
<div className="text-3xl font-bold text-[var(--primary)]">
{formatPrice(industry.price)}
</div>
<p className="mt-1 text-sm text-[var(--muted-foreground)]">
일회성 구매 (평생 이용)
</p>
<ul className="mt-4 space-y-2 text-sm">
<li>AI 프롬프트 {industry.skills.length}종</li>
<li>실전 활용 가이드 (PDF)</li>
<li>한국 시장 특화 예시</li>
<li>무료 업데이트 (1년)</li>
</ul>
<CheckoutButton
productSlug={industry.slug}
label={`${formatPrice(industry.price)} 구매하기`}
className="mt-6"
/>
</div>
</div>
</div>
<NewsletterCta />
</div>
</div>
);
}
Step 3: 빌드 확인
pnpm build
Step 4: 커밋
cd /Users/nbs22/\(Claude\)/\(claude\).projects/business-builder
git add projects/koreaai-hub/src/app/skills/ projects/koreaai-hub/src/components/checkout-button.tsx
git commit -m "feat(koreaai-hub): 스킬패키지 상세 페이지 + 체크아웃 버튼"
git pull --rebase origin main && git push origin main
Task 11: 결제 성공 페이지
Files:
- Create:
projects/koreaai-hub/src/app/checkout/success/page.tsx
Step 1: 결제 성공 페이지
src/app/checkout/success/page.tsx:
import type { Metadata } from "next";
import Link from "next/link";
export const metadata: Metadata = {
title: "결제 완료 — KoreaAI Hub",
};
export default function CheckoutSuccessPage() {
return (
<div className="flex min-h-[60vh] items-center justify-center px-4">
<div className="max-w-md text-center">
<div className="text-6xl">🎉</div>
<h1 className="mt-4 text-3xl font-bold">결제가 완료되었습니다!</h1>
<p className="mt-4 text-[var(--muted-foreground)]">
구매하신 스킬패키지의 다운로드 링크가 이메일로 전송됩니다.
이메일이 도착하지 않으면 스팸함을 확인해주세요.
</p>
<div className="mt-8 flex flex-col gap-3">
<Link
href="/skills"
className="rounded-lg bg-[var(--primary)] px-6 py-3 font-semibold text-[var(--primary-foreground)] hover:opacity-90"
>
다른 패키지 둘러보기
</Link>
<Link
href="/"
className="rounded-lg border border-[var(--border)] px-6 py-3 font-semibold hover:bg-[var(--muted)]"
>
홈으로 돌아가기
</Link>
</div>
</div>
</div>
);
}
Step 2: 빌드 확인
pnpm build
Step 3: 커밋
cd /Users/nbs22/\(Claude\)/\(claude\).projects/business-builder
git add projects/koreaai-hub/src/app/checkout/
git commit -m "feat(koreaai-hub): 결제 성공 페이지"
git pull --rebase origin main && git push origin main
Task 12: 관리자 대시보드 — 구독자/주문 현황
Files:
- Create:
projects/koreaai-hub/src/app/admin/page.tsx - Create:
projects/koreaai-hub/src/app/admin/layout.tsx - Create:
projects/koreaai-hub/src/app/api/admin/stats/route.ts
Step 1: 관리자 통계 API
src/app/api/admin/stats/route.ts:
import { NextRequest, NextResponse } from "next/server";
import { db } from "@/lib/db";
import { subscribers, orders } from "@/lib/db/schema";
import { eq, sql } from "drizzle-orm";
const ADMIN_KEY = process.env.ADMIN_API_KEY || "admin-secret-key";
export async function GET(request: NextRequest) {
const authHeader = request.headers.get("x-admin-key");
if (authHeader !== ADMIN_KEY) {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
}
const [
totalSubscribers,
activeSubscribers,
totalOrders,
paidOrders,
revenue,
] = await Promise.all([
db.select({ count: sql<number>`count(*)` }).from(subscribers),
db.select({ count: sql<number>`count(*)` }).from(subscribers).where(eq(subscribers.status, "active")),
db.select({ count: sql<number>`count(*)` }).from(orders),
db.select({ count: sql<number>`count(*)` }).from(orders).where(eq(orders.status, "paid")),
db.select({ total: sql<number>`COALESCE(sum(amount), 0)` }).from(orders).where(eq(orders.status, "paid")),
]);
return NextResponse.json({
subscribers: {
total: totalSubscribers[0].count,
active: activeSubscribers[0].count,
},
orders: {
total: totalOrders[0].count,
paid: paidOrders[0].count,
revenue: revenue[0].total,
},
});
}
Step 2: 관리자 레이아웃
src/app/admin/layout.tsx:
export default function AdminLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<div className="min-h-screen bg-[var(--muted)]">
<div className="border-b border-[var(--border)] bg-[var(--background)] px-4 py-3">
<div className="mx-auto flex max-w-6xl items-center justify-between">
<h1 className="text-lg font-bold">KoreaAI Hub Admin</h1>
</div>
</div>
<div className="mx-auto max-w-6xl px-4 py-8">{children}</div>
</div>
);
}
Step 3: 관리자 대시보드 페이지
src/app/admin/page.tsx:
"use client";
import { useEffect, useState } from "react";
interface Stats {
subscribers: { total: number; active: number };
orders: { total: number; paid: number; revenue: number };
}
export default function AdminPage() {
const [stats, setStats] = useState<Stats | null>(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
async function fetchStats() {
try {
const res = await fetch("/api/admin/stats", {
headers: { "x-admin-key": "admin-secret-key" },
});
if (res.ok) {
setStats(await res.json());
}
} finally {
setLoading(false);
}
}
fetchStats();
}, []);
if (loading) return <div className="text-center py-20">로딩 중...</div>;
if (!stats) return <div className="text-center py-20">데이터 로드 실패</div>;
const cards = [
{ label: "총 구독자", value: stats.subscribers.total, sub: `활성: ${stats.subscribers.active}` },
{ label: "총 주문", value: stats.orders.total, sub: `결제완료: ${stats.orders.paid}` },
{ label: "총 매출", value: `$${(stats.orders.revenue / 100).toFixed(2)}`, sub: "USD" },
];
return (
<div>
<h2 className="text-2xl font-bold mb-6">대시보드</h2>
<div className="grid grid-cols-1 gap-4 md:grid-cols-3">
{cards.map((card) => (
<div key={card.label} className="rounded-xl border border-[var(--border)] bg-[var(--background)] p-6">
<div className="text-sm text-[var(--muted-foreground)]">{card.label}</div>
<div className="mt-2 text-3xl font-bold">{card.value}</div>
<div className="mt-1 text-xs text-[var(--muted-foreground)]">{card.sub}</div>
</div>
))}
</div>
</div>
);
}
Step 4: 빌드 확인
pnpm build
Step 5: 커밋
cd /Users/nbs22/\(Claude\)/\(claude\).projects/business-builder
git add projects/koreaai-hub/src/app/admin/ projects/koreaai-hub/src/app/api/admin/
git commit -m "feat(koreaai-hub): 관리자 대시보드 — 구독자/주문/매출 통계"
git pull --rebase origin main && git push origin main
Task 13: SEO 설정 — sitemap, robots, og:image
Files:
- Create:
projects/koreaai-hub/src/app/sitemap.ts - Create:
projects/koreaai-hub/src/app/robots.ts - Create:
projects/koreaai-hub/src/app/opengraph-image.tsx(또는 정적 이미지)
Step 1: sitemap.ts
src/app/sitemap.ts:
import type { MetadataRoute } from "next";
import { industries } from "@/lib/data/industries";
export default function sitemap(): MetadataRoute.Sitemap {
const baseUrl = process.env.NEXT_PUBLIC_APP_URL || "https://koreaai.hub";
const industryPages = industries.map((ind) => ({
url: `${baseUrl}/skills/${ind.slug}`,
lastModified: new Date(),
changeFrequency: "weekly" as const,
priority: 0.8,
}));
return [
{ url: baseUrl, lastModified: new Date(), changeFrequency: "daily", priority: 1.0 },
{ url: `${baseUrl}/skills`, lastModified: new Date(), changeFrequency: "weekly", priority: 0.9 },
{ url: `${baseUrl}/newsletter`, lastModified: new Date(), changeFrequency: "weekly", priority: 0.9 },
...industryPages,
];
}
Step 2: robots.ts
src/app/robots.ts:
import type { MetadataRoute } from "next";
export default function robots(): MetadataRoute.Robots {
const baseUrl = process.env.NEXT_PUBLIC_APP_URL || "https://koreaai.hub";
return {
rules: {
userAgent: "*",
allow: "/",
disallow: ["/admin/", "/api/"],
},
sitemap: `${baseUrl}/sitemap.xml`,
};
}
Step 3: 빌드 확인
pnpm build
Step 4: 커밋
cd /Users/nbs22/\(Claude\)/\(claude\).projects/business-builder
git add projects/koreaai-hub/src/app/sitemap.ts projects/koreaai-hub/src/app/robots.ts
git commit -m "feat(koreaai-hub): SEO 설정 — sitemap.xml + robots.txt"
git pull --rebase origin main && git push origin main
Task 14: GitHub 레포 생성 + Vercel 배포
Files:
- Create:
projects/koreaai-hub/vercel.json
Step 1: GitHub 레포 생성
cd /Users/nbs22/\(Claude\)/\(claude\).projects/business-builder/projects/koreaai-hub
git init
git add .
git commit -m "feat: KoreaAI Hub MVP 초기 구현"
gh repo create migkjy/koreaai-hub --private --source . --push
Step 2: vercel.json 생성
vercel.json:
{
"framework": "nextjs",
"buildCommand": "pnpm build",
"installCommand": "pnpm install"
}
Step 3: Vercel 프로젝트 연결
cd /Users/nbs22/\(Claude\)/\(claude\).projects/business-builder/projects/koreaai-hub
vercel link
Step 4: 환경변수 설정 (Vercel)
# Turso
vercel env add TURSO_DB_URL
vercel env add TURSO_DB_TOKEN
# Lemon Squeezy
vercel env add LEMON_SQUEEZY_API_KEY
vercel env add LEMON_SQUEEZY_STORE_ID
vercel env add LEMON_SQUEEZY_WEBHOOK_SECRET
# Brevo
vercel env add BREVO_API_KEY
vercel env add BREVO_LIST_ID
# App
vercel env add NEXT_PUBLIC_APP_URL
vercel env add ADMIN_API_KEY
Step 5: Preview 배포
vercel
Expected: Preview URL 생성. 프로덕션은 CEO 승인 후에만.
Step 6: 커밋 (business-builder 레포)
cd /Users/nbs22/\(Claude\)/\(claude\).projects/business-builder
git add projects/koreaai-hub/vercel.json projects/koreaai-hub/CLAUDE.md
git commit -m "feat(koreaai-hub): Vercel 배포 설정 + GitHub 레포 연결"
git pull --rebase origin main && git push origin main
Task 15: Lemon Squeezy 상품 등록 + DB 시드
Files:
- Create:
projects/koreaai-hub/scripts/seed-products.ts
Step 1: 상품 시드 스크립트
scripts/seed-products.ts:
import { drizzle } from "drizzle-orm/libsql";
import { createClient } from "@libsql/client";
import { products } from "../src/lib/db/schema";
const client = createClient({
url: process.env.TURSO_DB_URL!,
authToken: process.env.TURSO_DB_TOKEN,
});
const db = drizzle(client);
const seedProducts = [
{
slug: "marketing",
name: "마케팅/광고 AI 스킬패키지",
description: "AI 기반 광고 카피라이팅, 타겟 고객 분석, 캠페인 자동화 등 마케팅 실무 AI 스킬 10종",
industry: "마케팅",
price: 1900,
currency: "USD",
type: "single" as const,
status: "active" as const,
},
{
slug: "finance",
name: "금융/핀테크 AI 스킬패키지",
description: "AI 리스크 분석, 신용평가 자동화, 금융 보고서 생성 등 금융 특화 AI 스킬 10종",
industry: "금융",
price: 1900,
currency: "USD",
type: "single" as const,
status: "active" as const,
},
{
slug: "hr",
name: "HR/채용 AI 스킬패키지",
description: "AI 이력서 스크리닝, 면접 질문 생성, 노무 상담 등 HR 특화 AI 스킬 10종",
industry: "HR",
price: 1900,
currency: "USD",
type: "single" as const,
status: "active" as const,
},
{
slug: "manufacturing",
name: "제조업 AI 스킬패키지",
description: "AI 품질검사, 공정 최적화, 예측 정비 등 제조업 특화 AI 스킬 10종",
industry: "제조",
price: 1900,
currency: "USD",
type: "single" as const,
status: "active" as const,
},
{
slug: "legal",
name: "법률/컨설팅 AI 스킬패키지",
description: "AI 계약서 검토, 판례 검색, 법률 의견서 생성 등 법률 특화 AI 스킬 10종",
industry: "법률",
price: 1900,
currency: "USD",
type: "single" as const,
status: "active" as const,
},
{
slug: "bundle",
name: "All-in-One AI 스킬패키지",
description: "전체 5개 산업군 × 10개 스킬 = 총 50개 AI 스킬 번들",
industry: "전체",
price: 4900,
currency: "USD",
type: "bundle" as const,
status: "active" as const,
},
];
async function seed() {
console.log("시드 데이터 삽입 시작...");
for (const product of seedProducts) {
await db.insert(products).values(product);
console.log(` ✅ ${product.name}`);
}
console.log("완료! 총 " + seedProducts.length + "개 상품 등록");
}
seed().catch(console.error);
Step 2: package.json에 시드 스크립트 추가
package.json의 scripts에 추가:
"db:seed": "tsx scripts/seed-products.ts"
tsx 설치:
pnpm add -D tsx
Step 3: 시드 실행
cd /Users/nbs22/\(Claude\)/\(claude\).projects/business-builder/projects/koreaai-hub
pnpm db:seed
Expected: 6개 상품 등록 완료
Step 4: Lemon Squeezy 대시보드에서 상품 생성 후 variant ID 업데이트
CEO 액션 필요: Lemon Squeezy 대시보드에서 6개 상품 생성 후 variant_id를 DB에 업데이트해야 결제 연동이 완성됩니다.
Step 5: 커밋
cd /Users/nbs22/\(Claude\)/\(claude\).projects/business-builder
git add projects/koreaai-hub/scripts/ projects/koreaai-hub/package.json
git commit -m "feat(koreaai-hub): 상품 시드 스크립트 — 5개 산업군 + 번들"
git pull --rebase origin main && git push origin main
Task 16: E2E 스모크 테스트
Step 1: 수동 스모크 테스트 체크리스트
다음 항목을 로컬(pnpm dev)에서 확인:
| # | 테스트 항목 | 기대 결과 |
|---|---|---|
| 1 | http://localhost:3000 메인 페이지 | Hero, Features, Skills, Newsletter CTA 모두 렌더링 |
| 2 | http://localhost:3000/newsletter | 뉴스레터 구독 페이지 렌더링, 이메일 입력 폼 동작 |
| 3 | http://localhost:3000/skills | 5개 산업군 카드 + 번들 배너 렌더링 |
| 4 | http://localhost:3000/skills/marketing | 마케팅 패키지 상세 — 10개 스킬 목록 + 구매 버튼 |
| 5 | 구독 폼 이메일 제출 | POST /api/subscribe → 201 응답 + DB 저장 |
| 6 | 빌드 성공 | pnpm build exit 0 |
| 7 | 반응형 (모바일) | 320px 뷰포트에서 레이아웃 깨지지 않음 |
Step 2: 빌드 최종 확인
cd /Users/nbs22/\(Claude\)/\(claude\).projects/business-builder/projects/koreaai-hub
pnpm build
Expected: 모든 페이지 빌드 성공
CEO 블로킹 항목 (Phase 1 완료 후)
| # | 항목 | 필요한 행동 |
|---|---|---|
| 1 | Lemon Squeezy 계정/스토어 생성 | CEO가 LS 가입 → Store 생성 → API Key 발급 |
| 2 | LS 상품 6개 등록 | LS 대시보드에서 수동 생성 → variant_id를 DB에 업데이트 |
| 3 | Brevo API Key | CEO Brevo 계정에서 발급 → env에 추가 |
| 4 | Brevo 리스트 생성 | 뉴스레터 리스트 생성 → BREVO_LIST_ID 설정 |
| 5 | 도메인 결정 | koreaai.hub 또는 다른 도메인 → Vercel 커스텀 도메인 |
| 6 | Turso DB 토큰 | turso db tokens create koreaai-hub 실행 |
| 7 | 프로덕션 배포 승인 | CEO 확인 후 vercel --prod |
예상 구현 시간
| Task | 예상 |
|---|---|
| Task 1-3: 프로젝트 초기화 + DB + UI | 15분 |
| Task 4-5: Brevo + Lemon Squeezy 연동 | 15분 |
| Task 6-8: 레이아웃 + 랜딩 + 뉴스레터 | 20분 |
| Task 9-11: 스킬패키지 페이지 + 결제 | 20분 |
| Task 12-13: 관리자 + SEO | 10분 |
| Task 14-16: 배포 + 시드 + 테스트 | 15분 |
| 합계 | ~95분 |
리뷰 로그
[koreaai-plan-pl 초안 작성] 2026-02-26T21:00:00+09:00
- L0 리서치 v4 + CEO 블로킹 해소 보고서 기반으로 L2 구현 플랜 작성
- 기술 스택: apppro-kr 기준 (Next.js 15 + Drizzle + Turso) + Lemon Squeezy + Brevo
- MVP 범위: 뉴스레터 구독 + 스킬패키지 5종 + 번들 + 결제 + 관리자 대시보드
- Phase 2 이후 항목 명확히 제외 (BizFlow API, 큐레이션, Featured Listing)
- 총 16개 태스크, bite-sized steps 포함
- CEO 블로킹 7건 정리 (LS 계정, Brevo API, 도메인 등)
- Lemon Squeezy SDK + Brevo Node SDK + Drizzle ORM 최신 API 참조