chore(blog): /work/blog 라우트·참조·메타 완전 제거 (2026-05-29 재정의)
- app/work/blog/, lib/blog-tools/ 폴더 삭제 - 홈·work 허브 카드/카피, footer 링크에서 블로그 자동화 제거 - layout.tsx keywords·description·JSON-LD Offer 제거 - refund 약관 상품 목록 정리, sitemap /services/blog 엔트리 제거 - next.config: /services/blog·/work/blog → /work 301 리다이렉트 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -90,7 +90,6 @@ export default function PublicShell({ children }: { children: React.ReactNode })
|
||||
<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/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>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
@@ -27,7 +27,6 @@ export const metadata: Metadata = {
|
||||
"유튜브 쇼츠 음악",
|
||||
"AI 뮤비",
|
||||
"음악 프롬프트",
|
||||
"블로그 자동화",
|
||||
"AI 사주",
|
||||
],
|
||||
authors: [{ name: "박재오", url: "https://jaengseung-made.com" }],
|
||||
@@ -74,14 +73,14 @@ const jsonLd = {
|
||||
email: 'bgg8988@gmail.com',
|
||||
telephone: '010-3907-1392',
|
||||
knowsAbout: ['Python', 'Java', 'Spring Boot', 'Next.js', 'AI 프롬프트', 'AI 자동화', '업무 자동화', 'ChatGPT', 'Claude'],
|
||||
description: '현직 엔지니어. AI 음악 구조 설계 팩, 블로그 자동화 팩, AI 사주 분석 등 AI 크리에이티브 도구를 직접 개발·운영합니다.',
|
||||
description: '현직 엔지니어. AI 음악 생성 개발 가이드 패키지, AI 사주 분석 등 AI 크리에이티브 도구를 직접 개발·운영합니다.',
|
||||
},
|
||||
{
|
||||
'@type': 'LocalBusiness',
|
||||
'@id': 'https://jaengseung-made.com/#business',
|
||||
name: '쟁승메이드',
|
||||
url: 'https://jaengseung-made.com',
|
||||
description: 'AI 음악 작곡·뮤비 구조 설계 팩, 블로그 자동화 팩, AI 사주 분석. 현직 엔지니어가 직접 설계·운영하는 AI 크리에이티브 스토어.',
|
||||
description: 'AI 음악 생성 개발 가이드 패키지, AI 사주 분석. 현직 엔지니어가 직접 설계·운영하는 AI 크리에이티브 스토어.',
|
||||
email: 'bgg8988@gmail.com',
|
||||
telephone: '010-3907-1392',
|
||||
priceRange: '₩',
|
||||
@@ -93,7 +92,6 @@ const jsonLd = {
|
||||
{ '@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: '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: '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: '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',
|
||||
|
||||
@@ -18,7 +18,7 @@ export default function RefundPage() {
|
||||
|
||||
<section>
|
||||
<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">
|
||||
전자상거래법 제17조 제2항 제5호에 따라, 디지털 콘텐츠는 제공이 개시된 이후 청약철회가 제한됩니다.
|
||||
회사는 구매 전 무료 샘플·미리보기를 제공하고, 구매 시 환불 제한 사항에 대한 소비자 동의를 확인합니다.
|
||||
|
||||
@@ -47,7 +47,6 @@ const CB_CARDS = [
|
||||
{ href: '/work/freelance', label: '외주 개발', desc: '맞춤 솔루션 · RPA·API 자동화 포함', key: 'freelance' },
|
||||
{ href: '/work/website', label: '웹사이트', desc: '기업·브랜드 사이트', key: 'website' },
|
||||
{ href: '/work/saju', label: 'AI 사주', desc: '12개 항목 무료 해석', key: 'saju' },
|
||||
{ href: '/work/blog', label: '블로그 자동화', desc: '수익 엔진 팩', key: 'blog' },
|
||||
];
|
||||
|
||||
export default function Home() {
|
||||
@@ -160,7 +159,7 @@ export default function Home() {
|
||||
맞춤 개발 사업부
|
||||
</h2>
|
||||
<p className="text-sm md:text-base text-white/70 mb-4">
|
||||
외주 · 웹사이트 · AI 사주 · 블로그 자동화
|
||||
외주 · 웹사이트 · AI 사주
|
||||
</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">
|
||||
@@ -366,11 +365,11 @@ export default function Home() {
|
||||
맞춤 개발이 필요하신가요?
|
||||
</h2>
|
||||
<p className="text-base md:text-lg text-white/70 max-w-2xl mx-auto leading-relaxed">
|
||||
7년차 백엔드 개발자가 직접 설계·개발·납품. 외주, 웹사이트, AI 사주, 블로그 자동화까지.
|
||||
7년차 백엔드 개발자가 직접 설계·개발·납품. 외주, 웹사이트, AI 사주까지.
|
||||
</p>
|
||||
</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) => (
|
||||
<Link
|
||||
key={card.key}
|
||||
|
||||
@@ -7,7 +7,6 @@ export default function sitemap(): MetadataRoute.Sitemap {
|
||||
return [
|
||||
{ url: base, lastModified: now, changeFrequency: 'weekly', priority: 1.0 },
|
||||
{ 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}/legal/terms`, 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';
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: 'Custom Build — 맞춤 개발 사업부',
|
||||
description: '7년차 백엔드 개발자가 직접 설계·개발·납품. 외주 · 웹사이트 · AI 사주 · 블로그 자동화.',
|
||||
title: '커스텀 외주 — 맞춤 개발',
|
||||
description: '7년차 백엔드 개발자가 직접 설계·개발·납품. 외주 · 웹사이트 · AI 사주.',
|
||||
};
|
||||
|
||||
export default function WorkLayout({ children }: { children: React.ReactNode }) {
|
||||
|
||||
@@ -25,12 +25,6 @@ const CARDS = [
|
||||
desc: 'AI 사주팔자 + 12개 항목 해석 (무료)',
|
||||
key: 'saju',
|
||||
},
|
||||
{
|
||||
href: '/work/blog',
|
||||
label: '블로그 자동화',
|
||||
desc: '수익 엔진 팩 · 자동화 마케팅 콘텐츠',
|
||||
key: 'blog',
|
||||
},
|
||||
];
|
||||
|
||||
export default function WorkHub() {
|
||||
@@ -67,13 +61,13 @@ export default function WorkHub() {
|
||||
맞춤 개발 사업부
|
||||
</h1>
|
||||
<p className="text-base md:text-lg text-white/70 max-w-2xl mx-auto leading-relaxed">
|
||||
7년차 백엔드 개발자가 직접 설계·개발·납품. 외주, 웹사이트, AI 사주, 블로그 자동화까지.
|
||||
7년차 백엔드 개발자가 직접 설계·개발·납품. 외주, 웹사이트, AI 사주까지.
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<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) => (
|
||||
<Link
|
||||
key={c.key}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -39,7 +39,9 @@ const nextConfig: NextConfig = {
|
||||
{ source: '/freelance', destination: '/work/freelance', permanent: true },
|
||||
{ source: '/services/website', destination: '/work/website', 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은 보류)
|
||||
{ source: '/saju', destination: '/work/saju', permanent: true },
|
||||
{ source: '/saju/input', destination: '/work/saju/input', permanent: true },
|
||||
|
||||
Reference in New Issue
Block a user