SAMPLE BRAND · DEVELOPER SPEC · v1 · 2026-05-04

카드별 상세 명세서 — 개발사 작업용

마스터 문서 v1의 12개 카드 각각에 대한 영향 범위·AS-IS 실측·TO-BE 정확 코드·수정 범위·검증 명령. 설명은 마스터 문서로 미루고 본 문서는 "할 것만" 다룹니다.

📑 카드 12개 빠른 점프

CARD #01 · BUG-01 · canonical 자가 참조 미적용

모든 서브 페이지의 canonical을 자기 URL로 변경

지금 바로 노출 손실

§1 영향 범위

441
canonicalised URL
246
작품 상세
88
about/
103
서비스 5종
경로 패턴건수예시
/works/view.php?idx=N246작품 상세 전체
/about/*.php88company.php, news.php, VOC.php 등
/webtoon/ 외 4종103webtoon 30, character 18, emoticon 18, video 19, campaign 18
기타4support/inquiry.php 등
샘플 URL 12건 (전체 441건 중 — Screaming Frog canonicals_canonicalised.csv)
https://example.com/about/company.php https://example.com/works/view.php?idx=1&cat_no=&rt_url=/index.php https://example.com/works/view.php?idx=89&cat_no=&rt_url=/index.php https://example.com/works/view.php?idx=588&cat_no=&rt_url=/index.php https://example.com/works/view.php?idx=2&cat_no=&rt_url=/index.php https://example.com/campaign/ https://example.com/works/view.php?idx=3&cat_no=&rt_url=/index.php https://example.com/about/news.php https://example.com/video/ https://example.com/about/VOC.php https://example.com/support/inquiry.php https://example.com/character/

§2 AS-IS (현재 구현 상태)

위 441개 URL의 SSR 응답에 canonical 태그가 모두 동일한 값으로 박혀 있음:

AS-IS — 모든 페이지 동일
<link rel="canonical" href="https://example.com">

예: /works/view.php?idx=803 페이지 응답을 curl로 받아도 위와 동일한 canonical이 반환됨. 즉 검색엔진·AI는 이 페이지가 홈의 사본이라고 판단.

§3 TO-BE (권장 구현)

TO-BE — 자기 URL 자가 참조
<?php $current_url = "https://example.com" . $_SERVER['REQUEST_URI']; ?> <link rel="canonical" href="<?= htmlspecialchars($current_url, ENT_QUOTES) ?>">

주의: $_SERVER['REQUEST_URI']는 쿼리스트링을 포함한 전체 경로를 반환. /works/view.php?idx=803&cat_no=&rt_url=/index.php 그대로 canonical 값으로 사용. 쿼리스트링 정규화(예: cat_no·rt_url 빈 파라미터 제거)는 별도 정책 결정 후 적용.

§4 수정 범위

  • 대상 파일: 공통 헤더 PHP include 1개 (예상 — 실제 파일명은 개발사 확인 필요)
  • 예상 수정 라인: 약 3~5줄
  • 영향 페이지: 공통 헤더를 include하는 모든 페이지 = 위 441개 + 홈
  • 홈 페이지 처리: 홈은 이미 https://example.com이 canonical로 정확. 변경 후에도 결과 동일해야 함 (회귀 테스트 필요)

§5 검증 명령

아래 명령을 작업 후 모두 실행하여 결과 확인:

curl로 5개 URL의 canonical 추출
for url in \ "https://example.com/" \ "https://example.com/webtoon/" \ "https://example.com/about/company.php" \ "https://example.com/works/view.php?idx=803" \ "https://example.com/campaign/"; do echo "=== $url ===" curl -sL "$url" | grep -oE '<link[^>]*rel="canonical"[^>]*>' done
  • 각 URL의 canonical 값이 자기 URL과 일치 (홈 제외)
  • 홈 canonical은 https://example.com 또는 https://example.com/ 유지
  • 임의 작품 5개 추가 검증 → 모두 자기 URL
CARD #02 · BUG-02 · SSR 메타 분리 + JS 주입 제거

페이지별 title·meta description·OG를 SSR로 변수화

지금 바로 노출 손실
DEPENDENCY

본 작업은 CARD #01과 반드시 같은 배포에 묶어야 합니다. #01만 먼저 배포되면 검색엔진이 441개 페이지를 새로 발견했다가 SSR 메타가 전부 동일해서 다시 같은 페이지로 묶어버립니다.

§1 영향 범위

442
동일 description 페이지
444
동일 title 페이지 (446개 중)
438
JS로 title 갱신되는 페이지

모든 서브 페이지의 SSR HTML <title>·<meta name="description">·og:title·og:description이 동일. 작품 제목은 클라이언트 JS로 후속 주입되어 있으나 AI 크롤러는 인식 불가.

§2 AS-IS

AS-IS — 442개 페이지 공통 description

"(주)샘플 브랜드은 웹툰 및 무빙툰 제작부터 캐릭터 개발, 애니메이션, 브랜드 이모티콘까지 아우르는 종합 콘텐츠 제작 업체입니다. 귀사의 스토리를 매력적인 콘텐츠로 완성해 드립니다."

AS-IS — 444개 페이지 공통 title
<title>(주)샘플 브랜드 | 웹툰·무빙툰·브랜드 이모티콘·캐릭터 종합 제작</title>
AS-IS — JS 주입 (작품 페이지 예)

SSR HTML title은 위 공통 텍스트, 페이지 로드 후 JS가 작품명으로 갱신:

// 클라이언트 JS — AI 크롤러 미실행 영역 document.title = work_title + " | (주)샘플 브랜드 | ...";

§3 TO-BE — 페이지 유형별 SSR 템플릿

5개 페이지 유형별로 SSR 분기:

TO-BE 1 — 홈 (현재 유지)
<title>(주)샘플 브랜드 | 웹툰·무빙툰·브랜드 이모티콘·캐릭터 종합 제작</title> <meta name="description" content="(현재 카피 유지)">
TO-BE 2 — 서비스 카테고리 (5종 각각)
<!-- /webtoon/ 예시 --> <title>브랜드웹툰 제작 | (주)샘플 브랜드</title> <meta name="description" content="기업·기관 메시지를 웹툰 형식으로 기획·제작. 정부·대기업 캠페인 다수."> <meta property="og:title" content="브랜드웹툰 제작 | (주)샘플 브랜드"> <meta property="og:description" content="(위와 동일)">
TO-BE 3 — 작품 상세 (works/view.php 템플릿)
<title><?= htmlspecialchars($work['title']) ?> | (주)샘플 브랜드</title> <meta name="description" content="<?= htmlspecialchars($work['client_name']) ?>를 위한 <?= htmlspecialchars($work['genre']) ?> 제작 사례. (주)샘플 브랜드이 기획·제작한 브랜드 콘텐츠."> <meta property="og:title" content="<?= htmlspecialchars($work['title']) ?> | (주)샘플 브랜드"> <meta property="og:description" content="(위와 동일)"> <meta property="og:image" content="<?= $work['cover_image'] ?>">
TO-BE 4 — about/news/VOC 등
<!-- 예: /about/company.php --> <title>회사 소개 | (주)샘플 브랜드</title> <meta name="description" content="2012년 설립 콘텐츠 제작 에이전시. 웹툰·무빙툰·캐릭터·이모티콘·영상 종합 제작. 정부·대기업 다수 협업.">
TO-BE 5 — JS 주입 코드 제거

SSR로 정확한 title이 들어가므로, 클라이언트 JS의 document.title = ... 로직은 제거. 잔존 시 페이지 로드 후 의도와 다른 갱신이 일어날 수 있음.

§4 수정 범위

  • 공통 헤더 PHP: title/meta/OG 변수화 로직 추가 (변수: $page_title, $page_description, $og_image)
  • 각 페이지 PHP: 페이지 진입부에서 위 변수 설정
  • works/view.php: DB의 작품 정보를 변수에 매핑
  • 제거: 클라이언트 JS의 document.title / meta 갱신 로직

§5 검증 명령

JS 미실행 SSR 응답 확인 (curl)
for url in \ "https://example.com/webtoon/" \ "https://example.com/works/view.php?idx=803" \ "https://example.com/works/view.php?idx=89" \ "https://example.com/about/company.php"; do echo "=== $url ===" curl -sL "$url" | grep -oE '(<title>[^<]*</title>|name="description" content="[^"]*")' done
  • 각 URL의 SSR title이 모두 다름 (홈 + 서비스 5 + 작품 N개)
  • 각 URL의 SSR description이 모두 다름
  • OG title/description도 SSR HTML에 정확히 박혀 있음
  • JS 비활성화 브라우저(또는 --disable-javascript 플래그)에서 페이지 진입 시 title 변경 없음
  • SNS 공유 디버거(Facebook/Twitter/Kakao) 통과
CARD #03 · BUG-03 · 보안 응답 헤더 6종 부재

.htaccess에 보안 헤더 6종 추가

지금 바로 신뢰 저하

§1 영향 범위

사이트 전체 응답 (5,907 URL — 페이지·이미지·CSS·JS 포함). 모든 응답이 보안 헤더 6종 누락 상태.

4,997
200 응답 URL
F
현재 securityheaders.com 등급 (예상)
A+
목표 등급

§2 AS-IS

AS-IS — 응답 헤더 누락 (curl -I 결과 발췌)
HTTP/1.1 200 OK Server: Apache X-XSS-Protection: 1; mode=block Set-Cookie: PHPSESSID=... Cache-Control: no-cache, no-store, must-revalidate # 누락: # - Strict-Transport-Security # - Content-Security-Policy # - X-Frame-Options # - X-Content-Type-Options # - Referrer-Policy # - Permissions-Policy

§3 TO-BE

TO-BE — .htaccess (웹 루트) 추가
<IfModule mod_headers.c> Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains" Header always set X-Content-Type-Options "nosniff" Header always set X-Frame-Options "SAMEORIGIN" Header always set Referrer-Policy "strict-origin-when-cross-origin" Header always set Permissions-Policy "geolocation=(), microphone=(), camera=(), payment=()" # CSP는 Report-Only로 1~2주 운영 후 정식 전환 Header always set Content-Security-Policy-Report-Only "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval' https:; style-src 'self' 'unsafe-inline' https:; img-src 'self' data: https:; font-src 'self' data: https:; connect-src 'self' https:; frame-src 'self' https://www.youtube.com https://player.vimeo.com;" </IfModule>

CSP 주의: Report-Only 모드는 위반 시에도 차단되지 않고 콘솔에 경고만 출력. 1~2주 운영 후 콘솔 모니터링 결과로 외부 도메인 화이트리스트 보강 → 정식 Content-Security-Policy로 전환.

§4 수정 범위

  • 대상 파일: 웹 루트 .htaccess 1개
  • 예상 수정 라인: 약 10줄 추가
  • 의존성: 없음 (CARD #01·#02와 같은 배포에 함께 처리 권장)

§5 검증 명령

응답 헤더 확인
curl -sI https://example.com/ | grep -iE "(strict-transport|content-security|x-frame|x-content|referrer-policy|permissions)"
  • 위 6개 헤더가 모두 응답에 포함
  • securityheaders.com 점수 A 이상
  • ssllabs.com SSL 등급 A 이상
  • 1주차 모니터링: 브라우저 콘솔 CSP 위반 경고 분석 → 외부 도메인 화이트리스트 보강
  • YouTube·Vimeo 임베드 영상 정상 재생 (해당 시)
CARD #04 · A-01 · sitemap.xml 동적 생성 + N-03 stale 정리

sitemap.php 동적 생성 + robots.txt에 등록

지금 바로 노출 손실
공통 원인

현재 사이트에 sitemap.xml은 404이지만, 외부에 알려진 옛 sitemap이 1,364건의 URL을 갖고 있고 그 중 910건이 이미 redirect/404인 stale 상태입니다. 옛 sitemap이 가리키는 URL들은 모두 http://www.example.com/wp-content/... — 과거 WordPress 사이트 잔재. CARD #11(HTTP→HTTPS)과 같은 원인이므로 같이 처리하면 효율적입니다.

§1 영향 범위

404
현재 /sitemap.xml 응답
1,364
옛 sitemap에 남은 URL
454
옛 sitemap 중 200 응답
910
옛 sitemap 중 redirect (stale)
옛 sitemap의 redirect 샘플 — 모두 wp-content/ 잔재 (Screaming Frog sitemaps_all.csv)
http://www.example.com/wp-content/uploads/2022/09/변화리더어워즈-웹툰-1.png http://www.example.com/wp-content/uploads/2022/09/품질경영마인드-내재화-웹툰-1.png http://www.example.com/wp-content/uploads/2019/01/1.jpg http://www.example.com/wp-content/uploads/2022/09/1-5.png

§2 AS-IS

AS-IS — /sitemap.xml 응답
$ curl -sI https://example.com/sitemap.xml HTTP/1.1 404 Not Found
AS-IS — robots.txt
User-agent: * Allow: /

Sitemap 선언 부재.

§3 TO-BE

TO-BE 1 — /sitemap.php (PHP 동적 생성 스크립트)
<?php header('Content-Type: application/xml; charset=utf-8'); echo '<?xml version="1.0" encoding="UTF-8"?>' . "\n"; echo '<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">' . "\n"; // 정적 페이지 $static = [ ['/', 'weekly', '1.0'], ['/about/company.php', 'monthly', '0.8'], ['/about/news.php', 'weekly', '0.7'], ['/about/VOC.php', 'monthly', '0.6'], ['/campaign/', 'monthly', '0.8'], ['/webtoon/', 'monthly', '0.8'], ['/character/', 'monthly', '0.8'], ['/emoticon/', 'monthly', '0.8'], ['/video/', 'monthly', '0.8'], ]; foreach ($static as [$path, $freq, $pri]) { echo " <url><loc>https://example.com$path</loc><changefreq>$freq</changefreq><priority>$pri</priority></url>\n"; } // 작품 (DB 쿼리) $db = new PDO('mysql:host=...', '...', '...'); $stmt = $db->query("SELECT idx, updated_at FROM works WHERE deleted = 0 ORDER BY idx"); while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) { $url = "https://example.com/works/view.php?idx=" . $row['idx']; $lastmod = date('Y-m-d', strtotime($row['updated_at'])); echo " <url><loc>$url</loc><lastmod>$lastmod</lastmod><changefreq>monthly</changefreq><priority>0.7</priority></url>\n"; } echo '</urlset>';
TO-BE 2 — .htaccess rewrite
RewriteEngine On RewriteRule ^sitemap\.xml$ /sitemap.php [L]
TO-BE 3 — robots.txt 갱신
User-agent: * Allow: / Disallow: /sub/ Disallow: /popup/ Disallow: /include/ Disallow: /admin/ Sitemap: https://example.com/sitemap.xml

§4 수정 범위

  • 신규 파일: /sitemap.php (웹 루트)
  • 수정 파일: .htaccess (rewrite 추가) + robots.txt (Sitemap 라인 추가, Disallow 4종 추가)
  • 제외 대상: redirect URL, 4xx URL, /sub/·/popup/·/include/·/admin/ 디렉터리
  • 옛 sitemap 처리: 외부 캐시(Google·Naver) 갱신은 새 sitemap을 GSC·서치어드바이저에 등록하면 자동 진행

§5 검증 명령

curl -sI https://example.com/sitemap.xml | head -1 curl -s https://example.com/sitemap.xml | head -20 curl -s https://example.com/sitemap.xml | grep -oE '<loc>[^<]*</loc>' | wc -l # robots.txt 확인 curl -s https://example.com/robots.txt
  • /sitemap.xml 200 응답
  • XML 형식 유효 (xmllint 또는 GSC 등록 시 통과)
  • sitemap 내 URL 수: 정적 9개 + 작품 약 50건 = 약 60건 내외 (작품 DB에 따라 변동)
  • robots.txt에 Sitemap 라인 포함
  • Google Search Console에 sitemap 등록 → "성공" 상태
  • 대형 포털 서치어드바이저 등록 → 정상 색인
  • sitemap 내 모든 URL batch curl → 200 응답률 100% (redirect/404 0건)
CARD #05 · A-02 · llms.txt 신규 생성

/llms.txt 정적 파일 배치

이번 달 콘텐츠 자산

§1 영향 범위

사이트 입구 1개 파일 신설. AI 크롤러(GPTBot·ClaudeBot·PerplexityBot 등) 참조 대상.

§2 AS-IS

AS-IS — /llms.txt 응답
$ curl -sI https://example.com/llms.txt HTTP/1.1 404 Not Found

§3 TO-BE

TO-BE — /llms.txt (웹 루트, 정적 파일)
# (주)샘플 브랜드 (Sample Brand) > 웹툰·무빙툰·브랜드 이모티콘·캐릭터·홍보 영상 종합 콘텐츠 제작 에이전시. > 정부기관·대기업 캠페인 다수 진행. 2012년 1월 27일 설립. ## 회사 - [회사 소개](https://example.com/about/company.php) - [News](https://example.com/about/news.php) - [문의](https://example.com/about/VOC.php) ## 서비스 - [Campaign — 캠페인 기획·제작](https://example.com/campaign/) - [Webtoon — 브랜드웹툰](https://example.com/webtoon/) - [Character — 캐릭터 개발](https://example.com/character/) - [Emoticon — 대형 메신저 플랫폼 브랜드 이모티콘](https://example.com/emoticon/) - [Video — 홍보 영상](https://example.com/video/) ## 주요 포트폴리오 - (작품 상위 20건 — 발주처 선정 후 추가) ## 연락처 - 전화: 02-0000-0000 - 이메일: [email protected] - 주소: (주소 비공개) - 사업자등록번호: 000-00-00000

§4 수정 범위

  • 신규 파일: /llms.txt (웹 루트, MIME type text/plain)
  • 본문 작성: 발주처 → 소요유 협업 (회사 정보는 확보됨, 포트폴리오 20건은 발주처 선정 필요)
  • 개발사 작업: 파일 배치 + Apache MIME 설정 확인 (.txttext/plain으로 응답되는지)

§5 검증 명령

curl -sI https://example.com/llms.txt | head -3 curl -s https://example.com/llms.txt | head -10
  • 200 응답
  • Content-Type: text/plain; charset=utf-8 (또는 text/markdown)
  • 본문이 마크다운 형식으로 정상 응답
  • 한글 인코딩 깨짐 없음 (UTF-8 BOM 없는 상태로 저장)
CARD #06 · A-03 · Organization JSON-LD (홈)

홈 페이지에 회사 구조화 데이터 추가

이번 달 신뢰 저하

§1 영향 범위

홈 페이지 1개 (https://example.com/) — 단, 공통 footer include 활용 시 모든 페이지에서 동일 schema 노출 가능.

446
전체 페이지 (모두 schema 0건)
0
현재 schema 보유 페이지

§2 AS-IS

AS-IS — 홈 페이지 schema 검색
$ curl -s https://example.com/ | grep -c 'application/ld+json' 0

회사 정보(상호·대표·주소·사업자번호·전화·이메일)는 footer에 raw 텍스트로 노출되어 있으나 schema화 0건.

§3 TO-BE

아래 정보는 실측 확보된 값으로 채워져 있음 — 발주처 사전 전달 필요는 sameAs 소셜 URL 4종뿐:

TO-BE — 홈 </body> 직전 추가
<script type="application/ld+json"> { "@context": "https://schema.org", "@type": "Organization", "@id": "https://example.com/#organization", "name": "주식회사 샘플 브랜드", "alternateName": ["샘플 브랜드", "EXAMPLE", "JG"], "url": "https://example.com/", "logo": "https://example.com/images/sns_link.png", "founder": { "@type": "Person", "name": "홍길동" }, "foundingDate": "20XX-XX-XX", "address": { "@type": "PostalAddress", "streetAddress": "(주소 비공개)", "addressLocality": "서초구", "addressRegion": "지자체 A", "addressCountry": "KR" }, "contactPoint": [{ "@type": "ContactPoint", "telephone": "+82-2-0000-0000", "faxNumber": "+82-503-8379-2072", "email": "[email protected]", "contactType": "customer service", "areaServed": "KR", "availableLanguage": ["ko"] }], "taxID": "000-00-00000", "sameAs": [ "(Instagram URL — 발주처 전달)", "(YouTube URL — 발주처 전달)", "(Naver Blog URL — 발주처 전달)", "(LinkedIn URL — 발주처 전달)" ], "knowsAbout": [ "브랜드웹툰 제작", "무빙툰 제작", "캐릭터 개발", "대형 메신저 플랫폼 이모티콘 제작", "홍보 영상 제작" ], "description": "웹툰·무빙툰·브랜드 이모티콘·캐릭터·영상·애니메이션 종합 콘텐츠 제작과 작가 에이전시 운영, AI 숏폼웹툰 플랫폼 '위툰' 서비스를 제공합니다." } </script>

§4 수정 범위

  • 대상 파일: 홈 페이지 PHP의 </body> 직전 또는 공통 footer include
  • 발주처 사전 전달: 공식 소셜 채널 URL 4종 (운영 중인 것만)
  • 회사 기본 정보: 모두 확보됨 (사업자번호 000-00-00000, 대표 홍길동, 설립일 20XX-XX-XX, 주소·전화·이메일)

§5 검증 명령

curl -s https://example.com/ | grep -A 50 'application/ld+json' | head -60
CARD #07 · A-04 · Service JSON-LD 5종

5개 서비스 페이지에 Service schema 추가

이번 달 노출 손실

§1 영향 범위

서비스URL
Campaignhttps://example.com/campaign/
Webtoonhttps://example.com/webtoon/
Characterhttps://example.com/character/
Emoticonhttps://example.com/emoticon/
Videohttps://example.com/video/

§2 AS-IS

AS-IS — 5개 페이지 모두 schema 0건
for url in /campaign/ /webtoon/ /character/ /emoticon/ /video/; do echo "$url: $(curl -s https://example.com$url | grep -c 'application/ld+json')" done # 결과 /campaign/: 0 /webtoon/: 0 /character/: 0 /emoticon/: 0 /video/: 0

§3 TO-BE

각 페이지 </body> 직전 추가. 동일 구조, name·serviceType·description·url만 변경:

TO-BE — Webtoon 예시 (5개 모두 동일 구조)
<script type="application/ld+json"> { "@context": "https://schema.org", "@type": "Service", "@id": "https://example.com/webtoon/#service", "name": "브랜드웹툰 제작", "serviceType": "Brand Webtoon Production", "provider": { "@id": "https://example.com/#organization" }, "areaServed": { "@type": "Country", "name": "South Korea" }, "description": "기업·기관 메시지를 웹툰 형식으로 기획·제작하는 종합 서비스. 시나리오·스토리보드·작화·편집 일괄 진행.", "url": "https://example.com/webtoon/" } </script>
서비스nameserviceType
Campaign캠페인 기획·제작Marketing Campaign Production
Webtoon브랜드웹툰 제작Brand Webtoon Production
Character브랜드 캐릭터 개발Brand Character Design
Emoticon대형 메신저 플랫폼 브랜드 이모티콘 제작KakaoTalk Brand Emoticon Production
Video홍보 영상 제작Promotional Video Production

description 본문은 발주처 사전 전달(서비스당 1~2줄).

§4 수정 범위

  • 대상 파일: 5개 서비스 페이지 PHP (서비스별 단일 파일 또는 공통 템플릿 + 파라미터)
  • 발주처 사전 전달: 5개 서비스 description 1~2줄씩

§5 검증 명령

for url in /campaign/ /webtoon/ /character/ /emoticon/ /video/; do echo "=== $url ===" curl -s https://example.com$url | grep -A 15 'application/ld+json' | head -20 done
  • 5개 페이지 모두 JSON-LD 1개씩 존재
  • 5개 URL Google Rich Results Test 통과
  • provider@id가 CARD #06의 Organization을 정확히 참조
CARD #08 · A-05 · CreativeWork JSON-LD (작품 상세)

works/view.php 템플릿에 CreativeWork schema 추가

이번 달 콘텐츠 자산
DB 컬럼 사전 확인 필요

작품 DB(works 테이블)에 다음 컬럼 존재 여부를 발주처 → 개발사가 확인해야 함: title, client_name, genre, published_date, cover_image. 누락 컬럼은 데이터 보강 작업 별도 견적.

§1 영향 범위

246
작품 상세 페이지
0
현재 CreativeWork schema
작품 페이지 샘플 10건
https://example.com/works/view.php?idx=1&cat_no=&rt_url=/index.php https://example.com/works/view.php?idx=89&cat_no=&rt_url=/index.php https://example.com/works/view.php?idx=588&cat_no=&rt_url=/index.php https://example.com/works/view.php?idx=2&cat_no=&rt_url=/index.php https://example.com/works/view.php?idx=3&cat_no=&rt_url=/index.php https://example.com/works/view.php?idx=803&cat_no=&rt_url=/index.php https://example.com/works/view.php?idx=597&cat_no=&rt_url=/index.php https://example.com/works/view.php?idx=794&cat_no=&rt_url=/index.php https://example.com/works/view.php?idx=811&cat_no=&rt_url=/index.php https://example.com/works/view.php?idx=7&cat_no=&rt_url=/index.php

§2 AS-IS

AS-IS — 작품 페이지 schema 검색
$ curl -s "https://example.com/works/view.php?idx=803" | grep -c 'application/ld+json' 0

§3 TO-BE

TO-BE — works/view.php 템플릿
<script type="application/ld+json"> { "@context": "https://schema.org", "@type": "CreativeWork", "name": "<?= htmlspecialchars($work['title'], ENT_QUOTES) ?>", "creator": { "@id": "https://example.com/#organization" }, "sourceOrganization": { "@type": "Organization", "name": "<?= htmlspecialchars($work['client_name'], ENT_QUOTES) ?>" }, "genre": "<?= htmlspecialchars($work['genre'], ENT_QUOTES) ?>", "datePublished": "<?= $work['published_date'] ?>", "image": "<?= $work['cover_image'] ?>", "url": "https://example.com/works/view.php?idx=<?= $work['idx'] ?>" } </script>

§4 수정 범위

  • 대상 파일: /works/view.php 1개 (단일 템플릿이 246개 페이지에 적용)
  • DB 사전 점검: 5개 컬럼 존재 + 데이터 채워진 비율
  • 데이터 보강: 컬럼이 없거나 데이터 비어있는 경우 별도 작업 (대량 — 246건)

§5 검증 명령

for idx in 1 89 588 2 3; do echo "=== idx=$idx ===" curl -s "https://example.com/works/view.php?idx=$idx" | grep -A 12 'application/ld+json' done
  • 임의 작품 5개 모두 CreativeWork JSON-LD 존재
  • 5개 모두 Google Rich Results Test 통과
  • name·sourceOrganization.name·genre·datePublished 모두 비어있지 않음
  • creator@id가 Organization 참조
CARD #09 · A-06 · FAQPage 신설 + schema

/faq/ 페이지 신설 + FAQPage schema

이번 달 콘텐츠 자산

§1 영향 범위

신규 페이지 1개 (/faq/) + GNB 메뉴 항목 1개.

§2 AS-IS

AS-IS — /faq/ 페이지 부재
$ curl -sI https://example.com/faq/ HTTP/1.1 404 Not Found

§3 TO-BE

TO-BE 1 — /faq/index.php (페이지 본문)
<!-- 페이지 본문 — 10개 Q&A 시각 노출 --> <h1>자주 묻는 질문</h1> <dl> <dt>브랜드웹툰 제작 기간은 보통 얼마나 걸리나요?</dt> <dd>(답변 본문 100~200자)</dd> <!-- 10건 반복 --> </dl>
TO-BE 2 — FAQPage JSON-LD
<script type="application/ld+json"> { "@context": "https://schema.org", "@type": "FAQPage", "mainEntity": [ { "@type": "Question", "name": "브랜드웹툰 제작 기간은 보통 얼마나 걸리나요?", "acceptedAnswer": { "@type": "Answer", "text": "(답변 본문 100~200자)" } }, { "@type": "Question", "name": "무빙툰과 일반 웹툰의 차이는 무엇인가요?", "acceptedAnswer": { "@type": "Answer", "text": "..." } } /* ... 총 10건 */ ] } </script>

10개 질문 (소요유 답변 초안 작성 → 발주처 검수):

  1. 브랜드웹툰 제작 기간은 보통 얼마나 걸리나요?
  2. 무빙툰과 일반 웹툰의 차이는 무엇인가요?
  3. 대형 메신저 플랫폼 브랜드 이모티콘 제작 비용은 어떻게 산정되나요?
  4. 캐릭터 IP 저작권은 누구에게 귀속되나요?
  5. 정부기관·공공 입찰용 견적서 발급이 가능한가요?
  6. 시안 수정은 몇 회까지 가능한가요?
  7. 영상 제작 시 성우·배경음악 라이선스도 포함되나요?
  8. 제작 진행 중 중간 보고는 어떻게 받나요?
  9. 제작된 콘텐츠를 자사 SNS·광고에 자유롭게 사용할 수 있나요?
  10. 첫 미팅 전에 준비해야 할 자료가 있나요?

§4 수정 범위

  • 신규 파일: /faq/index.php
  • GNB 메뉴: 공통 헤더 또는 footer에 "자주 묻는 질문" 링크 추가
  • 발주처 작업: 답변 10건 검수 (초안은 소요유 작성)
  • sitemap 갱신: CARD #04에서 자동 반영

§5 검증 명령

curl -sI https://example.com/faq/ | head -1 curl -s https://example.com/faq/ | grep -c 'application/ld+json' curl -s https://example.com/faq/ | grep -c '"@type": "Question"'
  • /faq/ 200 응답
  • JSON-LD 1개 존재
  • Question 10개, Answer 10개 모두 추출
  • Google Rich Results Test → FAQPage 인식, 10건 표시
  • 본문 시각 노출 = JSON-LD 텍스트 일치 (Google 정책)
CARD #10 · A-07 · BreadcrumbList JSON-LD

모든 서브 페이지에 breadcrumb UI + JSON-LD

다음 분기 운영 효율

§1 영향 범위

모든 서브 페이지 (홈 제외 약 445개). 페이지 유형별 breadcrumb 구조 4종.

§2 AS-IS

시각 breadcrumb UI 없음. BreadcrumbList JSON-LD 0건.

§3 TO-BE

페이지 유형별 breadcrumb 구조:

페이지 유형breadcrumb
서비스 (예: /webtoon/)홈 › 브랜드웹툰
작품 상세 (works/view.php?idx=N)홈 › Works › {작품명}
About (/about/company.php)홈 › 회사 소개
News 상세홈 › News › {기사 제목}
TO-BE — 작품 상세 예시
<!-- 시각 UI --> <nav class="breadcrumb"> <a href="/">홈</a> > <a href="/works/">Works</a> > <span><?= htmlspecialchars($work['title']) ?></span> </nav> <!-- JSON-LD --> <script type="application/ld+json"> { "@context": "https://schema.org", "@type": "BreadcrumbList", "itemListElement": [ {"@type": "ListItem", "position": 1, "name": "홈", "item": "https://example.com/"}, {"@type": "ListItem", "position": 2, "name": "Works", "item": "https://example.com/works/"}, {"@type": "ListItem", "position": 3, "name": "<?= htmlspecialchars($work['title']) ?>"} ] } </script>

§4 수정 범위

  • 공통 컴포넌트: breadcrumb 렌더 함수 (PHP) + CSS
  • 각 템플릿: 페이지 진입부에서 breadcrumb 배열 설정 → 공통 컴포넌트 호출
  • 의존성: 작품 페이지는 CARD #02 (작품명 변수화) 완료 후 진행

§5 검증 명령

for url in /webtoon/ /about/company.php "/works/view.php?idx=803"; do echo "=== $url ===" curl -s "https://example.com$url" | grep -A 12 'BreadcrumbList' done
  • 각 페이지 유형별 breadcrumb 시각 표시 정상
  • JSON-LD BreadcrumbList 인식 (Rich Results Test)
  • 마지막 항목(현재 페이지)은 item 없이 name만 (Google 권장)
CARD #11 · N-01 · HTTP→HTTPS 내부 자원 530건

DB 본문 일괄 SQL 치환 + .htaccess 도메인 통일

지금 바로 신뢰 저하
CARD #04와 같은 원인

HTTP 530건과 sitemap stale 910건의 redirect URL이 모두 동일한 패턴(http://www.example.com/wp-content/...) — 과거 WordPress 사이트 잔재. 같은 SQL UPDATE로 둘 다 처리 가능합니다.

§1 영향 범위

530
HTTP 자원 참조
2
혼용 도메인 (www / non-www)
2
혼용 프로토콜 (http / https)

§2 AS-IS

AS-IS — DB 본문 내 자원 참조 패턴
http://www.example.com/wp-content/uploads/2022/09/...png ← 옛 WP 잔재 http://example.com/uploaded/... ← 옛 비-HTTPS https://www.example.com/uploaded/... ← 다른 도메인(www) https://example.com/uploaded/... ← 정상 (목표)

§3 TO-BE

TO-BE 1 — DB 본문 일괄 SQL 치환
-- 백업 먼저 CREATE TABLE works_backup_20260504 AS SELECT * FROM works; CREATE TABLE news_backup_20260504 AS SELECT * FROM news; -- works 테이블 본문 치환 UPDATE works SET content = REPLACE(content, 'http://www.example.com', 'https://example.com') WHERE content LIKE '%http://www.example.com%'; UPDATE works SET content = REPLACE(content, 'http://example.com', 'https://example.com') WHERE content LIKE '%http://example.com%'; UPDATE works SET content = REPLACE(content, 'https://www.example.com', 'https://example.com') WHERE content LIKE '%https://www.example.com%'; -- news, about 등 다른 콘텐츠 테이블 동일 처리 -- (실제 테이블 구조에 따라 적용)
TO-BE 2 — .htaccess www → non-www 통일
RewriteEngine On RewriteCond %{HTTP_HOST} ^www\.example\.com$ [NC] RewriteRule ^(.*)$ https://example.com/$1 [R=301,L] # HTTP → HTTPS (이미 적용되어 있으면 생략) RewriteCond %{HTTPS} off RewriteRule ^(.*)$ https://example.com/$1 [R=301,L]
TO-BE 3 — 정적 파일 경로 권장

템플릿·헤더에서 정적 자원 경로는 절대 URL 대신 상대 경로 사용. 향후 도메인 변경 시에도 깨지지 않음.

<!-- BAD --> <img src="https://example.com/uploaded/abc.jpg"> <!-- GOOD --> <img src="/uploaded/abc.jpg">

§4 수정 범위

  • DB 작업: works·news·about 등 콘텐츠 테이블의 본문 컬럼 SQL UPDATE
  • .htaccess: www → non-www 리다이렉트 1개 (이미 있으면 확인만)
  • 옛 WP 경로(/wp-content/uploads/) 처리: 실제 파일 존재 여부 확인 → 존재하면 그대로 / 부재하면 /uploaded/ 신규 경로로 마이그레이션
  • CMS 가이드: 향후 콘텐츠 입력 시 절대 URL 입력 방지 가이드 안내

§5 검증 명령

# 본문 내 HTTP 자원 grep for url in / /webtoon/ "/works/view.php?idx=803" "/works/view.php?idx=1"; do count=$(curl -sL "https://example.com$url" | grep -oE 'http://[^"\s]*' | grep -v 'http://schema.org' | wc -l) echo "$url: HTTP 자원 $count건" done # www 리다이렉트 확인 curl -sI "https://www.example.com/" | grep -iE "(location|http)" | head -3
  • 임의 5개 페이지 본문에 http:// 시작 자원 0건 (schema.org 제외)
  • www.example.com 접근 시 example.com으로 301 리다이렉트
  • 브라우저 콘솔 Mixed Content 경고 0건
  • Screaming Frog 재크롤 → "Security: HTTP URLs" 530 → 0
CARD #12 · N-02 · 이미지 최적화 1,472건

WebP 변환 + responsive srcset + lazy loading

이번 달 운영 효율

§1 영향 범위

1,472
100KB+ 이미지
1.2 GB
합계 용량
19.4 MB
최대 단일
2
현재 WebP
확장자건수
PNG623
JPG562
GIF285
WebP2 (변환 미적용 상태)
Top 10 무거운 이미지 (Screaming Frog images_over_100_kb.csv)
19.40 MB — https://www.example.com/wp-content/uploads/2021/11/1화_완성본_201014.jpg 17.88 MB — https://www.example.com/wp-content/uploads/2021/05/이연제약-사내-소통문화-개선-웹툰-EP-5_fin_210414.jpg 12.72 MB — https://www.example.com/wp-content/uploads/2021/11/현대코퍼레이션홀딩스-1화.jpg 10.34 MB — https://www.example.com/wp-content/uploads/2021/11/1화_210107.jpg 8.85 MB — https://www.example.com/wp-content/uploads/2021/11/4화20컷.jpg 8.53 MB — https://example.com/uploaded/webedit/2507/15c55d93ea03e924de48f5df915a6e6d5979708b.jpg 8.37 MB — https://example.com/uploaded/webedit/2512/1cba8a60b31fe0b77c9dbe5e4b740028321e85ce.jpg 8.37 MB — https://www.example.com/uploaded/webedit/2512/1cba8a60b31fe0b77c9dbe5e4b740028321e85ce.jpg (위와 동일 파일, 도메인만 다름 — CARD #11과 함께 통일) 8.00 MB — https://www.example.com/uploaded/webedit/2512/0e096880f82e388e56f21443439abf2a64dde901.jpg 8.00 MB — https://example.com/uploaded/webedit/2512/0e096880f82e388e56f21443439abf2a64dde901.jpg

§2 AS-IS

AS-IS — 이미지 사용 패턴 (예시)
<img src="https://www.example.com/wp-content/uploads/2021/11/1화_완성본_201014.jpg"> <!-- 19.4MB JPG, 다운스케일 없음, lazy loading 없음 -->

§3 TO-BE

TO-BE 1 — 이미지 일괄 변환 스크립트 (예: ImageMagick)
# 원본 보존 (롤백 대비) mkdir -p /backup/images_original_20260504 cp -r /var/www/html/uploaded /backup/images_original_20260504/ cp -r /var/www/html/wp-content/uploads /backup/images_original_20260504/ # WebP 변환 + 다운스케일 (최대 가로 1920px, 웹툰은 1200px) find /var/www/html/uploaded /var/www/html/wp-content/uploads \ \( -name "*.jpg" -o -name "*.png" \) -size +100k | while read f; do out="${f%.*}.webp" convert "$f" -resize '1920x1920>' -quality 80 "$out" done
TO-BE 2 — 템플릿 picture/srcset
<picture> <source srcset="/uploaded/webedit/2507/abc-768.webp 768w, /uploaded/webedit/2507/abc-1200.webp 1200w, /uploaded/webedit/2507/abc.webp 1920w" sizes="(max-width: 768px) 100vw, (max-width: 1200px) 80vw, 1200px" type="image/webp"> <img src="/uploaded/webedit/2507/abc.jpg" alt="작품 설명" loading="lazy" width="1200" height="800"> </picture>
TO-BE 3 — CMS 업로드 훅 (자동화)

CMS 업로드 시점에 자동 압축 + WebP 생성. PHP의 gd·imagick 또는 외부 cwebp 호출.

// 업로드 후 훅 function on_image_upload($source_path) { $im = new Imagick($source_path); $w = $im->getImageWidth(); // 다운스케일 if ($w > 1920) { $im->resizeImage(1920, 0, Imagick::FILTER_LANCZOS, 1); } // WebP 저장 $webp_path = preg_replace('/\.(jpg|png)$/i', '.webp', $source_path); $im->setImageFormat('webp'); $im->setImageCompressionQuality(80); $im->writeImage($webp_path); }

§4 수정 범위

  • 일괄 변환: /uploaded/ + /wp-content/uploads/ 1,472건 (원본 보존)
  • 템플릿 수정: <img> 사용처 모두 <picture> + srcset으로 (works/view.php, 서비스 페이지, 홈)
  • CMS 훅: 신규 업로드 자동 압축
  • 의존성: CARD #11과 함께 진행하면 도메인 통일도 같이 처리됨 (Top 10 중 절반이 www 잔재)
  • 목표: 합계 200MB 이하, 단일 최대 500KB

§5 검증 명령

# Screaming Frog 재크롤 후 비교 python3 tools/screamingfrog_digest.py clients/샘플 브랜드/03_geo-seo/screamingfrog/ # PageSpeed Insights (모바일) # https://pagespeed.web.dev/analysis?url=https://example.com
  • 100KB 초과 이미지 1,472 → 200 이하
  • 합계 용량 1.2GB → 200MB 이하
  • 단일 파일 최대 500KB 이하
  • WebP 비율 50% 이상
  • PageSpeed Insights 모바일 LCP 4초 이하
  • 임의 페이지 5곳 모바일 로딩 체감 개선