← 목록으로
2026-03-03plans

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 이슈

#파일이슈임팩트
Afooter-section.tsx:113aihubkorea.krkoreaaihub.kr 도메인 오타🔴 HIGH
Bsrc/app/robots.ts파일 없음 → /admin 크롤 차단 불가🔴 HIGH
Cdownload/[slug]/page.tsxgenerateMetadata 없음, JSON-LD 없음🔴 HIGH
Dblog/category/[slug]/page.tsxJSON-LD schema 없음🟡 MED
Eblog/tag/[slug]/page.tsxJSON-LD schema 없음 (추정)🟡 MED
Fblog/page.tsxBreadcrumbList JSON-LD 없음🟡 MED
GDB 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.krkoreaaihub.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.tsadmin 크롤 차단 → 크롤 예산 절약
footer 도메인 수정KoreaAI Hub ↔ apppro.kr 교차링크 정상화
download/[slug] 메타데이터리드마그넷 4개 페이지 검색 노출 개선
blog/category + tag schema컨텐츠 분류 구조 명확화 → 크롤 이해도 향상
blog index BreadcrumbList사이트 계층 구조 명확화
블로그 아티클 검증broken link 식별 → 내부링크 품질 보호
plans/2026/03/03/apppro-seo-optimization.md