apppro.kr SEO 최적화 구현 플랜
For Claude: REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
Goal: apppro.kr 기술 SEO 보완 — 누락 schema 추가, 크롤 제어, broken 링크 수정, download 페이지 메타데이터 강화
Architecture: Next.js 15 App Router. 모든 JSON-LD는 <script type="application/ld+json"> 인라인. 메타데이터는 export const metadata 또는 generateMetadata. robots.ts는 Next.js Route Handler 방식.
Tech Stack: Next.js 15, TypeScript, Tailwind CSS v4, Drizzle ORM + Turso
Working directory: projects/apppro-kr/current-site
Git remote: migkjy/apppro.kr- (GitHub, branch: master)
Deploy: Vercel (main/master → Preview, production 브랜치 → Production, PR 필요)
분석 결과: 이미 완료된 것
다음은 건드리지 않는다 (2026-03-02 완료):
- 전 페이지
title,description,openGraph,twitter,keywords,canonical - layout.tsx: Organization + WebSite + LocalBusiness JSON-LD
- home: ProfessionalService + FAQPage JSON-LD
- 서비스 4개 페이지: Service + FAQPage + BreadcrumbList JSON-LD
- about: AboutPage + Organization JSON-LD
- blog 인덱스: Blog JSON-LD
- blog [slug]: BlogPosting + BreadcrumbList JSON-LD
- download 허브: BreadcrumbList JSON-LD
발견된 SEO 이슈
| # | 파일 | 이슈 | 임팩트 |
|---|---|---|---|
| A | footer-section.tsx:113 | aihubkorea.kr → koreaaihub.kr 도메인 오타 | 🔴 HIGH |
| B | src/app/robots.ts | 파일 없음 → /admin 크롤 차단 불가 | 🔴 HIGH |
| C | download/[slug]/page.tsx | generateMetadata 없음, JSON-LD 없음 | 🔴 HIGH |
| D | blog/category/[slug]/page.tsx | JSON-LD schema 없음 | 🟡 MED |
| E | blog/tag/[slug]/page.tsx | JSON-LD schema 없음 (추정) | 🟡 MED |
| F | blog/page.tsx | BreadcrumbList JSON-LD 없음 | 🟡 MED |
| G | DB blog articles | 서비스 페이지가 링크하는 3개 블로그 포스트 존재 확인 필요 | 🟡 MED |
Task 1: robots.ts 생성
Files:
- Create:
src/app/robots.ts
Step 1: robots.ts 파일 생성
// src/app/robots.ts
import type { MetadataRoute } from "next";
export default function robots(): MetadataRoute.Robots {
return {
rules: [
{
userAgent: "*",
allow: "/",
disallow: ["/admin/", "/api/admin/"],
},
],
sitemap: "https://apppro.kr/sitemap.xml",
};
}
Step 2: 빌드 확인
Run: pnpm run build
Expected: ✅ No errors. /.next/server/app/robots.txt generated.
Step 3: 로컬 확인
Run: curl http://localhost:3000/robots.txt (or just check build output)
Expected:
User-agent: *
Allow: /
Disallow: /admin/
Disallow: /api/admin/
Sitemap: https://apppro.kr/sitemap.xml
Step 4: Commit
git add src/app/robots.ts
git commit -m "feat(seo): add robots.ts — block /admin from crawling"
Task 2: footer-section.tsx 도메인 버그 수정
Files:
- Modify:
src/components/sections/footer-section.tsx:113
현재 코드:
href="https://aihubkorea.kr?utm_source=apppro&utm_medium=footer&utm_campaign=ecosystem"
수정 후:
href="https://koreaaihub.kr?utm_source=apppro&utm_medium=footer&utm_campaign=ecosystem"
Step 1: Edit 적용
footer-section.tsx 113번 줄에서 aihubkorea.kr → koreaaihub.kr 수정.
Step 2: 빌드 확인
Run: pnpm run build
Expected: ✅ No errors.
Step 3: Commit
git add src/components/sections/footer-section.tsx
git commit -m "fix(seo): correct koreaaihub.kr domain typo in footer link"
Task 3: download/[slug] generateMetadata + JSON-LD
이 페이지는 현재 generateMetadata가 전혀 없어 리드마그넷별 개별 메타데이터가 없음.
Files:
- Modify:
src/app/download/[slug]/page.tsx
Step 1: 현재 파일 전체 읽기
src/app/download/[slug]/page.tsx 전체 읽기.
Step 2: generateMetadata 함수 추가
getLeadMagnet 함수 아래, export default async function DownloadPage 위에 삽입:
export async function generateMetadata({ params }: PageProps): Promise<Metadata> {
const resolvedParams = await params;
const decodedSlug = decodeURIComponent(resolvedParams.slug);
const leadMagnet = await getLeadMagnet(decodedSlug);
if (!leadMagnet) {
return { title: "자료를 찾을 수 없습니다" };
}
const title = `${leadMagnet.title} - 앱프로 무료 자료`;
const description = leadMagnet.description;
return {
title,
description,
openGraph: {
title,
description,
type: "website",
url: `https://apppro.kr/download/${leadMagnet.slug}`,
locale: "ko_KR",
siteName: "앱프로 AppPro",
images: [
{
url: "https://apppro.kr/apppro-logo.png",
width: 1200,
height: 600,
alt: title,
},
],
},
twitter: {
card: "summary_large_image",
title,
description,
images: ["https://apppro.kr/apppro-logo.png"],
},
alternates: {
canonical: `https://apppro.kr/download/${leadMagnet.slug}`,
},
};
}
Step 3: Metadata import 추가
파일 상단 import에 Metadata 추가:
import type { Metadata } from "next";
Step 4: JSON-LD 추가
return 문 안에서 <main> 앞에 JSON-LD script 삽입:
const jsonLd = {
"@context": "https://schema.org",
"@type": "DigitalDocument",
name: leadMagnet.title,
description: leadMagnet.description,
url: `https://apppro.kr/download/${leadMagnet.slug}`,
publisher: {
"@type": "Organization",
name: "앱프로",
url: "https://apppro.kr",
},
inLanguage: "ko-KR",
isAccessibleForFree: true,
};
const breadcrumbJsonLd = {
"@context": "https://schema.org",
"@type": "BreadcrumbList",
itemListElement: [
{ "@type": "ListItem", position: 1, name: "앱프로 홈", item: "https://apppro.kr" },
{ "@type": "ListItem", position: 2, name: "무료 자료", item: "https://apppro.kr/download" },
{ "@type": "ListItem", position: 3, name: leadMagnet.title, item: `https://apppro.kr/download/${leadMagnet.slug}` },
],
};
return (
<>
<script
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
/>
<script
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(breadcrumbJsonLd) }}
/>
<main ...>
...
</main>
<FooterSection />
</>
);
Step 5: 빌드 확인
Run: pnpm run build
Expected: ✅ No type errors. No build errors.
Step 6: Commit
git add src/app/download/[slug]/page.tsx
git commit -m "feat(seo): add generateMetadata + DigitalDocument + BreadcrumbList JSON-LD to download/[slug]"
Task 4: blog/category + blog/tag JSON-LD 추가
Files:
- Modify:
src/app/blog/category/[slug]/page.tsx - Modify:
src/app/blog/tag/[slug]/page.tsx
4a. blog/category/[slug]/page.tsx
Step 1: 파일 전체 읽기
Step 2: generateMetadata 함수 내 JSON-LD 변수 추가 + return 구문에 script 삽입
// export default async function CategoryPage 안에서, return 직전에 추가:
const breadcrumbJsonLd = {
"@context": "https://schema.org",
"@type": "BreadcrumbList",
itemListElement: [
{ "@type": "ListItem", position: 1, name: "앱프로 홈", item: "https://apppro.kr" },
{ "@type": "ListItem", position: 2, name: "블로그", item: "https://apppro.kr/blog" },
{ "@type": "ListItem", position: 3, name: category, item: `https://apppro.kr/blog/category/${slug}` },
],
};
const collectionJsonLd = {
"@context": "https://schema.org",
"@type": "CollectionPage",
name: `${category} - 앱프로 블로그`,
description: `${category} 카테고리의 블로그 글 모음`,
url: `https://apppro.kr/blog/category/${encodeURIComponent(category)}`,
publisher: {
"@type": "Organization",
name: "앱프로",
url: "https://apppro.kr",
},
};
return 구문 최상단 <> 아래에:
<script type="application/ld+json" dangerouslySetInnerHTML={{ __html: JSON.stringify(breadcrumbJsonLd) }} />
<script type="application/ld+json" dangerouslySetInnerHTML={{ __html: JSON.stringify(collectionJsonLd) }} />
4b. blog/tag/[slug]/page.tsx
Step 3: 파일 전체 읽기 후 동일한 패턴으로 추가
category 대신 tag 변수명 사용. URL은 /blog/tag/${slug}.
const breadcrumbJsonLd = {
"@context": "https://schema.org",
"@type": "BreadcrumbList",
itemListElement: [
{ "@type": "ListItem", position: 1, name: "앱프로 홈", item: "https://apppro.kr" },
{ "@type": "ListItem", position: 2, name: "블로그", item: "https://apppro.kr/blog" },
{ "@type": "ListItem", position: 3, name: `#${tag}`, item: `https://apppro.kr/blog/tag/${slug}` },
],
};
const collectionJsonLd = {
"@context": "https://schema.org",
"@type": "CollectionPage",
name: `#${tag} - 앱프로 블로그`,
description: `#${tag} 태그의 블로그 글 모음`,
url: `https://apppro.kr/blog/tag/${encodeURIComponent(tag)}`,
publisher: {
"@type": "Organization",
name: "앱프로",
url: "https://apppro.kr",
},
};
Step 4: 빌드 확인
Run: pnpm run build
Expected: ✅ No errors.
Step 5: Commit
git add src/app/blog/category/[slug]/page.tsx src/app/blog/tag/[slug]/page.tsx
git commit -m "feat(seo): add CollectionPage + BreadcrumbList JSON-LD to blog category/tag pages"
Task 5: blog/page.tsx BreadcrumbList 추가
Files:
- Modify:
src/app/blog/page.tsx
현재 blogListJsonLd(Blog 타입)는 있지만 BreadcrumbList가 없음.
Step 1: 기존 blogListJsonLd 아래에 추가:
const breadcrumbJsonLd = {
"@context": "https://schema.org",
"@type": "BreadcrumbList",
itemListElement: [
{ "@type": "ListItem", position: 1, name: "앱프로 홈", item: "https://apppro.kr" },
{ "@type": "ListItem", position: 2, name: "블로그", item: "https://apppro.kr/blog" },
],
};
Step 2: return 구문에서 기존 <script> 아래에 추가:
<script
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(breadcrumbJsonLd) }}
/>
Step 3: 빌드 + Commit
Run: pnpm run build
Expected: ✅ No errors.
git add src/app/blog/page.tsx
git commit -m "feat(seo): add BreadcrumbList JSON-LD to blog index page"
Task 6: DB 블로그 아티클 존재 확인 + 보고
서비스 페이지들이 링크하는 블로그 아티클이 실제로 DB에 존재하는지 확인.
Files: 없음 (DB 조회만)
Step 1: Turso DB에서 slug 존재 확인
turso db shell apppro-kr "SELECT slug, title, published FROM blog_posts WHERE slug IN ('mvp-development-4weeks-process', 'ai-automation-business-guide', 'government-support-app-development-guide');"
Step 2: 결과 분석
- 3개 모두 존재 + published=true → ✅ 완료
- 일부 없거나 published=false → 🚨 보고 필요
Step 3: 없는 경우 처리
없는 slug를 확인하고 다음 내용을 포함한 메모 파일 생성:
docs/plans/2026/03/03/missing-blog-articles.md
내용:
# 누락 블로그 아티클 — 게리 바이너척 작성 요청
서비스 페이지에서 링크하지만 DB에 없는 블로그 포스트:
| slug | 링크 위치 | 제목 (표시) |
|------|----------|-----------|
| mvp-development-4weeks-process | /services/mvp-development, /services/maintenance | MVP 4주 개발 실제 프로세스 공개 |
| ai-automation-business-guide | /services/ai-automation | AI 자동화 비즈니스 가이드 |
| government-support-app-development-guide | /services/government-support, /services/maintenance | 정부지원사업 앱 개발 6단계 가이드 |
**게리에게:** 위 slug로 블로그 포스트 작성 요청 (각 1500~2000자 기준)
각 포스트는 해당 서비스 페이지로 내부 링크 포함할 것.
Step 4: Commit (누락 시에만)
git add docs/plans/2026/03/03/missing-blog-articles.md
git commit -m "docs(seo): record missing blog articles referenced by service pages"
Task 7: 빌드 + Push + PR
Step 1: 최종 빌드
pnpm run build
Expected: ✅ No errors, no type errors.
Step 2: Push to master
git pull --rebase origin master
git push origin master
Step 3: PR 생성 (master → production)
gh pr create \
--title "feat(seo): technical SEO improvements — robots.txt, schemas, domain fix" \
--body "## Changes
- Add robots.ts: block /admin from crawling
- Fix footer domain bug: aihubkorea.kr → koreaaihub.kr
- Add generateMetadata + DigitalDocument + BreadcrumbList to download/[slug]
- Add CollectionPage + BreadcrumbList JSON-LD to blog/category and blog/tag pages
- Add BreadcrumbList JSON-LD to blog index
## SEO Impact
- Prevents crawl budget waste on admin pages
- Fixes broken ecosystem cross-link
- Enables per-resource metadata for 4 lead magnet download pages
- Completes structured data coverage across all content taxonomies
🤖 Generated with [Claude Code](https://claude.com/claude-code)" \
--base production \
--head master
Step 4: VP 보고
./scripts/vice-reply.sh "apppro.kr SEO 기술 개선 완료. PR (master→production) 생성. robots.txt 추가, 도메인 버그 수정, download/blog 페이지 schema 완성. 게리 블로그 콘텐츠 요청 필요 여부는 Task 6 결과 포함." seo
예상 SEO 효과
| 개선사항 | 기대 효과 |
|---|---|
| robots.ts | admin 크롤 차단 → 크롤 예산 절약 |
| footer 도메인 수정 | KoreaAI Hub ↔ apppro.kr 교차링크 정상화 |
| download/[slug] 메타데이터 | 리드마그넷 4개 페이지 검색 노출 개선 |
| blog/category + tag schema | 컨텐츠 분류 구조 명확화 → 크롤 이해도 향상 |
| blog index BreadcrumbList | 사이트 계층 구조 명확화 |
| 블로그 아티클 검증 | broken link 식별 → 내부링크 품질 보호 |