기술 블로그 및 문서를 위한 JSON-LD 구조화 데이터와 Sitemap 자동 생성Automated JSON-LD Structured Data and Sitemap Generation for Technical Docs
검색엔진 크롤러가 문서의 메타데이터를 정확히 파싱하도록 Schema.org(TechArticle, BlogPosting) JSON-LD와 Open Graph 태그를 주입하고, 실존 파일만 검증하여 sitemap.xml을 생성하는 자동화 기법입니다.Automating Schema.org (TechArticle, BlogPosting) JSON-LD injection, Open Graph metadata, and file-verified sitemap.xml generation so search engine crawlers accurately index static pages.
<head> 내의 <script type="application/ld+json"> 구조화 데이터와 Open Graph 메타 태그를 먼저 수집합니다. 또한 sitemap.xml에 이미 삭제된 파일(404)이나 리다이렉트 대상 URL이 포함되면 사이트 신뢰도가 하락하므로, 빌드 스크립트에서 디스크의 실제 HTML 파일 존재 여부를 대조하여 사이트맵과 JSON-LD를 동기화해야 합니다.<script type="application/ld+json"> structured data and Open Graph meta tags inside <head> before evaluating body HTML. Furthermore, listing deleted files (404) or redirected URLs in sitemap.xml degrades crawl budget and indexing trust. The build script should verify that target HTML files physically exist on disk before emitting sitemap entries and JSON-LD blocks.1. Schema.org JSON-LD 주입 및 파일 존재 검증 기반 Sitemap 빌더1. Implementing Schema.org JSON-LD Injection and File-Verified Sitemap Builder
각 문서의 제목, 요약, 발행일, 수정일을 TechArticle 스키마 사전으로 구성해 json.dumps로 직렬화하고, 사이트맵 생성 시에는 docs.json 및 posts.json의 항목이 실제 디스크 상의 .html 파일로 존재하는지 확인한 뒤 XML 엔트리를 작성합니다.
Assemble each document's title, summary, publication date, and modification date into a TechArticle dictionary serialized via json.dumps. When generating sitemap.xml, cross-check every entry in docs.json and posts.json against physical .html files on disk.
from __future__ import annotations
import json
from pathlib import Path
from xml.sax.saxutils import escape
SITE_ORIGIN = "https://www.lysc.kr"
STUDIO_NAME = "LYSC STUDIO"
def build_tech_article_json_ld(doc: dict) -> str:
"""기술 문서용 Schema.org TechArticle 구조화 데이터를 JSON 문자열로 생성합니다."""
canonical_url = f"{SITE_ORIGIN}/docs/{doc['category']}/{doc['slug']}"
payload = {
"@context": "https://schema.org",
"@type": "TechArticle",
"headline": doc["title"],
"description": doc.get("summary", ""),
"url": canonical_url,
"datePublished": doc.get("date", "2026-10-09"),
"dateModified": doc.get("updated", doc.get("date", "2026-10-09")),
"author": {"@type": "Organization", "name": STUDIO_NAME, "url": f"{SITE_ORIGIN}/"},
"publisher": {"@type": "Organization", "name": STUDIO_NAME, "url": f"{SITE_ORIGIN}/"},
"inLanguage": "ko",
}
return json.dumps(payload, ensure_ascii=False, indent=2)
def build_verified_sitemap(repo_root: Path, docs_index: list[dict]) -> str:
"""디스크에 실제 HTML 파일이 존재하는 문서만 선별하여 sitemap.xml을 생성합니다."""
entries: list[str] = []
for doc in docs_index:
html_file = repo_root / "docs" / doc["category"] / f"{doc['slug']}.html"
if not html_file.exists():
continue # 404 유발 항목 자동 배제
loc = escape(f"{SITE_ORIGIN}{doc['url']}")
lastmod = doc.get("updated") or doc.get("date") or "2026-10-09"
entries.append(
f" <url>\n"
f" <loc>{loc}</loc>\n"
f" <lastmod>{lastmod}</lastmod>\n"
f" <changefreq>monthly</changefreq>\n"
f" <priority>0.8</priority>\n"
f" </url>\n"
)
return (
'<?xml version="1.0" encoding="UTF-8"?>\n'
'<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">\n'
+ "".join(entries)
+ "</urlset>\n"
)
from __future__ import annotations
import json
from pathlib import Path
from xml.sax.saxutils import escape
SITE_ORIGIN = "https://www.lysc.kr"
STUDIO_NAME = "LYSC STUDIO"
def build_tech_article_json_ld(doc: dict) -> str:
"""Generate Schema.org TechArticle structured data as a JSON string."""
canonical_url = f"{SITE_ORIGIN}/docs/{doc['category']}/{doc['slug']}"
payload = {
"@context": "https://schema.org",
"@type": "TechArticle",
"headline": doc["title"],
"description": doc.get("summary", ""),
"url": canonical_url,
"datePublished": doc.get("date", "2026-10-09"),
"dateModified": doc.get("updated", doc.get("date", "2026-10-09")),
"author": {"@type": "Organization", "name": STUDIO_NAME, "url": f"{SITE_ORIGIN}/"},
"publisher": {"@type": "Organization", "name": STUDIO_NAME, "url": f"{SITE_ORIGIN}/"},
"inLanguage": "ko",
}
return json.dumps(payload, ensure_ascii=False, indent=2)
def build_verified_sitemap(repo_root: Path, docs_index: list[dict]) -> str:
"""Generate sitemap.xml including only documents whose HTML files exist on disk."""
entries: list[str] = []
for doc in docs_index:
html_file = repo_root / "docs" / doc["category"] / f"{doc['slug']}.html"
if not html_file.exists():
continue # Automatically exclude missing files that would return 404
loc = escape(f"{SITE_ORIGIN}{doc['url']}")
lastmod = doc.get("updated") or doc.get("date") or "2026-10-09"
entries.append(
f" <url>\n"
f" <loc>{loc}</loc>\n"
f" <lastmod>{lastmod}</lastmod>\n"
f" <changefreq>monthly</changefreq>\n"
f" <priority>0.8</priority>\n"
f" </url>\n"
)
return (
'<?xml version="1.0" encoding="UTF-8"?>\n'
'<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">\n'
+ "".join(entries)
+ "</urlset>\n"
)
2. 주요 SEO 메타 태그 및 구조화 데이터 필드 매핑2. Key SEO Meta Tags and Structured Data Field Mapping
| 메타 요소 / 필드 | 표준 규격 | 검증 기준 및 역할 |
|---|---|---|
<link rel="canonical"> | RFC 6596 | 쿼리스트링이나 .html 확장자를 제외한 정식 절대 주소(https://www.lysc.kr/...) 명시 |
@type: TechArticle | Schema.org | headline, datePublished, dateModified, author를 포함하여 기술 문서 리치 결과 노출 |
og:url / og:image | Open Graph Protocol | 소셜 공유 시 미리보기 카드 생성 (상대 경로가 아닌 https:// 전체 도메인 경로 필수) |
<lastmod> (sitemap.xml) | Sitemaps 0.9 | YYYY-MM-DD ISO 8601 포맷을 준수하며 실제 콘텐츠가 갱신된 날짜(updated)와 일치 |
| Meta Element / Field | Standard Specification | Validation Criteria and Role |
|---|---|---|
<link rel="canonical"> | RFC 6596 | Declares the canonical absolute URL (https://www.lysc.kr/...) without query strings or .html suffixes |
@type: TechArticle | Schema.org | Includes headline, datePublished, dateModified, and author for technical article indexing |
og:url / og:image | Open Graph Protocol | Generates social preview cards (requires full https:// origin URLs rather than relative paths) |
<lastmod> (sitemap.xml) | Sitemaps 0.9 | Follows ISO 8601 YYYY-MM-DD format and matches the document's updated date |
3. 실무 적용 및 주의사항3. Practical Considerations and Caveats
- JSON-LD 직렬화 시
json.dumps사용: 문자열 템플릿으로 JSON-LD를 직접 이어 붙이면 제목에 큰따옴표(")나 역슬래시(\)가 들어갈 때 구문 오류가 발생하므로 반드시 파이썬json.dumps()를 통해 안전하게 직렬화해야 합니다. - Open Graph 이미지 절대 경로 지정: 카카오톡, 디스코드, 슬랙 등 메신저 크롤러는
og:image에/image/main_s.png같은 루트 상대 경로를 넣으면 이미지를 불러오지 못하는 경우가 있으므로SITE_ORIGIN을 포함한 전체 URL을 기입합니다. - XML 특수문자 이스케이프: 사이트맵
<loc>태그 생성 시 URL에&등의 문자가 포함될 가능성에 대비해xml.sax.saxutils.escape로 감싸 XML 파서 오류를 예방합니다.
- Serialize JSON-LD via
json.dumps: Concatenating JSON-LD with raw string templates breaks when titles contain double quotes (") or backslashes (\); always serialize Python dictionaries withjson.dumps(). - Use Full Absolute URLs for
og:image: Social media crawlers (Discord, Slack, KakaoTalk) often fail to resolve root-relative image paths like/image/main_s.png, so always prependSITE_ORIGIN. - Escape XML Entities in Sitemaps: Wrap
<loc>URLs withxml.sax.saxutils.escapeto prevent XML parser errors if special characters appear.
구조화 데이터(JSON-LD)와 사이트맵 생성을 정적 빌드 스크립트의 마지막 단계로 자동화하면, 문서를 추가하거나 수정할 때마다 누락이나 오타 없이 검색엔진 최적화 규격을 일관되게 유지할 수 있습니다.
Automating JSON-LD structured data and file-verified sitemap generation as part of the static build pipeline keeps SEO metadata accurate across every content update.