- YAML 규칙 파일 4개 신규 생성 (html_css, accessibility, seo, performance_security) W3C, WCAG 2.0/2.1/2.2, OWASP, Google Search Essentials 공식 표준 기반 - rules/__init__.py: YAML 로더 + 캐싱 + 리로드 모듈 - html_css.py: 30개 폐기 요소, 100+개 폐기 속성을 YAML에서 동적 로드 - accessibility.py: WCAG 버전 선택 지원 (wcag_version 파라미터) - seo.py: title/description 길이, OG 필수 태그 등 임계값 YAML 로드 - performance_security.py: COOP/COEP/CORP 검사 추가, 정보 노출 헤더 검사 추가, TTFB/페이지 크기 임계값 YAML 로드 - PyYAML 의존성 추가 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
530 lines
18 KiB
YAML
530 lines
18 KiB
YAML
# ============================================================
|
|
# SEO (Search Engine Optimization) Rules
|
|
# Based on: Google Search Essentials, Schema.org, Core Web Vitals
|
|
# ============================================================
|
|
|
|
metadata:
|
|
name: "SEO Standards"
|
|
version: "1.0.0"
|
|
last_updated: "2026-02-13"
|
|
sources:
|
|
- name: "Google Search Essentials"
|
|
url: "https://developers.google.com/search/docs/essentials"
|
|
description: "Google's core guidelines for search visibility"
|
|
- name: "Google Search Central - Technical Requirements"
|
|
url: "https://developers.google.com/search/docs/crawling-indexing"
|
|
- name: "Core Web Vitals"
|
|
url: "https://developers.google.com/search/docs/appearance/core-web-vitals"
|
|
- name: "Schema.org"
|
|
url: "https://schema.org/"
|
|
description: "Structured data vocabulary"
|
|
- name: "Open Graph Protocol"
|
|
url: "https://ogp.me/"
|
|
- name: "Lighthouse SEO Audit"
|
|
url: "https://developer.chrome.com/docs/lighthouse/seo/"
|
|
|
|
# ============================================================
|
|
# 1. Title & Meta Tags
|
|
# ============================================================
|
|
rules:
|
|
# --- Essential Meta Tags ---
|
|
- id: "seo-title-tag"
|
|
name: "Title Tag"
|
|
description: "Page must have a unique, descriptive <title> tag"
|
|
severity: "critical"
|
|
category: "meta"
|
|
standard: "Google Search Essentials"
|
|
check_type: "meta_tag_check"
|
|
details:
|
|
tag: "title"
|
|
requirements:
|
|
- "Must be present and non-empty"
|
|
- "Should be 30-60 characters for optimal display"
|
|
- "Should be unique across the site"
|
|
- "Should accurately describe page content"
|
|
max_length: 60
|
|
min_length: 10
|
|
|
|
- id: "seo-meta-description"
|
|
name: "Meta Description"
|
|
description: "Page should have a descriptive meta description"
|
|
severity: "major"
|
|
category: "meta"
|
|
standard: "Google Search Essentials"
|
|
check_type: "meta_tag_check"
|
|
details:
|
|
tag: '<meta name="description">'
|
|
requirements:
|
|
- "Should be 120-160 characters for optimal display"
|
|
- "Should be unique across the site"
|
|
- "Should accurately summarize page content"
|
|
max_length: 160
|
|
min_length: 50
|
|
|
|
- id: "seo-meta-viewport"
|
|
name: "Viewport Meta Tag"
|
|
description: "Page must have viewport meta tag for mobile compatibility"
|
|
severity: "critical"
|
|
category: "meta"
|
|
standard: "Google Mobile-First Indexing"
|
|
check_type: "meta_tag_check"
|
|
details:
|
|
tag: '<meta name="viewport">'
|
|
recommended_value: "width=device-width, initial-scale=1"
|
|
|
|
- id: "seo-charset"
|
|
name: "Character Encoding"
|
|
description: "Page must declare character encoding"
|
|
severity: "major"
|
|
category: "meta"
|
|
standard: "HTML Living Standard"
|
|
check_type: "meta_tag_check"
|
|
details:
|
|
tag: '<meta charset="UTF-8">'
|
|
|
|
- id: "seo-lang-attribute"
|
|
name: "HTML Language Attribute"
|
|
description: "HTML element should have lang attribute"
|
|
severity: "major"
|
|
category: "meta"
|
|
standard: "Google Search Essentials"
|
|
check_type: "attribute_check"
|
|
details:
|
|
element: "html"
|
|
attribute: "lang"
|
|
description: "Helps search engines serve language-appropriate results"
|
|
|
|
# --- Canonical & Duplicate Content ---
|
|
- id: "seo-canonical-url"
|
|
name: "Canonical URL"
|
|
description: "Page should have a canonical URL to prevent duplicate content"
|
|
severity: "major"
|
|
category: "meta"
|
|
standard: "Google Search Central"
|
|
check_type: "link_tag_check"
|
|
details:
|
|
tag: '<link rel="canonical">'
|
|
requirements:
|
|
- "Should be an absolute URL"
|
|
- "Should point to the preferred version of the page"
|
|
- "Must be self-referencing or point to a valid page"
|
|
|
|
- id: "seo-hreflang"
|
|
name: "Hreflang Tags"
|
|
description: "Multilingual pages should have hreflang annotations"
|
|
severity: "minor"
|
|
category: "meta"
|
|
standard: "Google Search Central - Internationalization"
|
|
check_type: "link_tag_check"
|
|
details:
|
|
tag: '<link rel="alternate" hreflang="...">'
|
|
description: "Tells Google which language versions exist for a page"
|
|
|
|
# --- Robots Control ---
|
|
- id: "seo-meta-robots"
|
|
name: "Meta Robots Tag"
|
|
description: "Check for meta robots directives"
|
|
severity: "major"
|
|
category: "crawling"
|
|
standard: "Google Search Central"
|
|
check_type: "meta_tag_check"
|
|
details:
|
|
tag: '<meta name="robots">'
|
|
valid_values:
|
|
- "index"
|
|
- "noindex"
|
|
- "follow"
|
|
- "nofollow"
|
|
- "noarchive"
|
|
- "nosnippet"
|
|
- "max-snippet"
|
|
- "max-image-preview"
|
|
- "max-video-preview"
|
|
warning_values:
|
|
- value: "noindex"
|
|
message: "Page is blocked from indexing"
|
|
- value: "nofollow"
|
|
message: "Links on this page will not be followed"
|
|
- value: "none"
|
|
message: "Page is blocked from indexing and links won't be followed"
|
|
|
|
# ============================================================
|
|
# 2. Content Structure
|
|
# ============================================================
|
|
- id: "seo-heading-structure"
|
|
name: "Heading Structure"
|
|
description: "Page should have proper heading hierarchy for SEO"
|
|
severity: "major"
|
|
category: "content"
|
|
standard: "Google Search Essentials"
|
|
check_type: "heading_check"
|
|
details:
|
|
checks:
|
|
- id: "has-h1"
|
|
description: "Page should have exactly one H1 tag"
|
|
severity: "critical"
|
|
- id: "h1-not-empty"
|
|
description: "H1 tag should not be empty"
|
|
severity: "critical"
|
|
- id: "heading-hierarchy"
|
|
description: "Headings should follow logical hierarchy (no skipping levels)"
|
|
severity: "major"
|
|
- id: "heading-keywords"
|
|
description: "Headings should contain relevant keywords"
|
|
severity: "minor"
|
|
- id: "no-multiple-h1"
|
|
description: "Page should not have multiple H1 tags"
|
|
severity: "major"
|
|
|
|
- id: "seo-image-alt"
|
|
name: "Image Alt Text"
|
|
description: "Images should have descriptive alt attributes for SEO"
|
|
severity: "major"
|
|
category: "content"
|
|
standard: "Google Search Essentials - Images"
|
|
check_type: "image_check"
|
|
details:
|
|
checks:
|
|
- id: "has-alt"
|
|
description: "All images should have alt attribute"
|
|
severity: "critical"
|
|
- id: "alt-not-empty"
|
|
description: "Alt text should not be empty (unless decorative)"
|
|
severity: "major"
|
|
- id: "alt-not-filename"
|
|
description: "Alt text should not be just a filename"
|
|
severity: "minor"
|
|
- id: "alt-not-too-long"
|
|
description: "Alt text should be under 125 characters"
|
|
max_length: 125
|
|
severity: "minor"
|
|
|
|
- id: "seo-internal-links"
|
|
name: "Internal Linking"
|
|
description: "Page should have internal links for crawlability"
|
|
severity: "minor"
|
|
category: "content"
|
|
standard: "Google Search Central - Links"
|
|
check_type: "link_check"
|
|
details:
|
|
checks:
|
|
- id: "has-internal-links"
|
|
description: "Page should contain internal links"
|
|
- id: "no-broken-links"
|
|
description: "Internal links should not return 404"
|
|
- id: "descriptive-anchor"
|
|
description: "Link anchor text should be descriptive (not 'click here')"
|
|
|
|
- id: "seo-content-length"
|
|
name: "Content Length"
|
|
description: "Page should have sufficient text content"
|
|
severity: "minor"
|
|
category: "content"
|
|
standard: "SEO Best Practice"
|
|
check_type: "content_check"
|
|
details:
|
|
min_word_count: 300
|
|
description: "Pages with thin content may rank poorly"
|
|
|
|
# ============================================================
|
|
# 3. Technical SEO
|
|
# ============================================================
|
|
- id: "seo-robots-txt"
|
|
name: "Robots.txt"
|
|
description: "Site should have a valid robots.txt file"
|
|
severity: "major"
|
|
category: "crawling"
|
|
standard: "Google Search Central - Robots.txt"
|
|
check_type: "file_check"
|
|
details:
|
|
path: "/robots.txt"
|
|
checks:
|
|
- id: "exists"
|
|
description: "robots.txt file should exist"
|
|
- id: "valid-syntax"
|
|
description: "robots.txt should have valid syntax"
|
|
- id: "not-blocking-important"
|
|
description: "Should not block important resources (CSS, JS, images)"
|
|
- id: "has-sitemap-reference"
|
|
description: "Should reference XML sitemap"
|
|
|
|
- id: "seo-sitemap-xml"
|
|
name: "XML Sitemap"
|
|
description: "Site should have a valid XML sitemap"
|
|
severity: "major"
|
|
category: "crawling"
|
|
standard: "Google Search Central - Sitemaps"
|
|
check_type: "file_check"
|
|
details:
|
|
paths:
|
|
- "/sitemap.xml"
|
|
- "/sitemap_index.xml"
|
|
checks:
|
|
- id: "exists"
|
|
description: "XML sitemap should exist"
|
|
- id: "valid-xml"
|
|
description: "Sitemap should be valid XML"
|
|
- id: "referenced-in-robots"
|
|
description: "Sitemap should be referenced in robots.txt"
|
|
|
|
- id: "seo-https"
|
|
name: "HTTPS"
|
|
description: "Site should be served over HTTPS"
|
|
severity: "critical"
|
|
category: "security_seo"
|
|
standard: "Google Search Essentials"
|
|
check_type: "protocol_check"
|
|
details:
|
|
description: "HTTPS is a confirmed Google ranking signal"
|
|
|
|
- id: "seo-mobile-friendly"
|
|
name: "Mobile Friendliness"
|
|
description: "Page should be mobile-friendly (mobile-first indexing)"
|
|
severity: "critical"
|
|
category: "mobile"
|
|
standard: "Google Mobile-First Indexing"
|
|
check_type: "mobile_check"
|
|
details:
|
|
checks:
|
|
- id: "viewport-meta"
|
|
description: "Has viewport meta tag"
|
|
- id: "responsive-design"
|
|
description: "Uses responsive CSS (media queries or fluid layout)"
|
|
- id: "no-horizontal-scroll"
|
|
description: "No horizontal scrolling at mobile widths"
|
|
- id: "readable-font-size"
|
|
description: "Font size is readable without zooming (>= 16px base)"
|
|
- id: "tap-targets"
|
|
description: "Tap targets are at least 48x48 CSS pixels"
|
|
|
|
- id: "seo-page-speed"
|
|
name: "Page Load Speed"
|
|
description: "Page should load quickly for better SEO"
|
|
severity: "major"
|
|
category: "performance_seo"
|
|
standard: "Google Core Web Vitals"
|
|
check_type: "performance_check"
|
|
details:
|
|
metrics:
|
|
- name: "LCP"
|
|
description: "Largest Contentful Paint"
|
|
good: "<= 2.5s"
|
|
needs_improvement: "<= 4.0s"
|
|
poor: "> 4.0s"
|
|
- name: "INP"
|
|
description: "Interaction to Next Paint"
|
|
good: "<= 200ms"
|
|
needs_improvement: "<= 500ms"
|
|
poor: "> 500ms"
|
|
- name: "CLS"
|
|
description: "Cumulative Layout Shift"
|
|
good: "<= 0.1"
|
|
needs_improvement: "<= 0.25"
|
|
poor: "> 0.25"
|
|
|
|
- id: "seo-url-structure"
|
|
name: "URL Structure"
|
|
description: "URLs should be clean and descriptive"
|
|
severity: "minor"
|
|
category: "technical"
|
|
standard: "Google Search Essentials - URL Structure"
|
|
check_type: "url_check"
|
|
details:
|
|
checks:
|
|
- id: "readable-url"
|
|
description: "URL should be human-readable (not query strings)"
|
|
- id: "no-underscores"
|
|
description: "URLs should use hyphens, not underscores"
|
|
- id: "lowercase"
|
|
description: "URLs should be lowercase"
|
|
- id: "no-excessive-depth"
|
|
description: "URL path should not be excessively deep (> 4 levels)"
|
|
max_depth: 4
|
|
|
|
- id: "seo-redirect-check"
|
|
name: "Redirect Handling"
|
|
description: "Check for proper redirect implementation"
|
|
severity: "major"
|
|
category: "technical"
|
|
standard: "Google Search Central - Redirects"
|
|
check_type: "redirect_check"
|
|
details:
|
|
checks:
|
|
- id: "no-redirect-chains"
|
|
description: "Avoid redirect chains (>2 hops)"
|
|
- id: "use-301"
|
|
description: "Permanent redirects should use 301 status"
|
|
- id: "no-meta-refresh-redirect"
|
|
description: "Avoid meta refresh redirects"
|
|
|
|
# ============================================================
|
|
# 4. Structured Data
|
|
# ============================================================
|
|
- id: "seo-structured-data"
|
|
name: "Structured Data (Schema.org)"
|
|
description: "Page should include structured data for rich results"
|
|
severity: "minor"
|
|
category: "structured_data"
|
|
standard: "Schema.org / Google Structured Data"
|
|
check_type: "structured_data_check"
|
|
details:
|
|
formats:
|
|
- "JSON-LD (recommended)"
|
|
- "Microdata"
|
|
- "RDFa"
|
|
common_types:
|
|
- type: "WebSite"
|
|
description: "Site-level information with search action"
|
|
- type: "Organization"
|
|
description: "Organization/company information"
|
|
- type: "BreadcrumbList"
|
|
description: "Breadcrumb navigation structure"
|
|
- type: "Article"
|
|
description: "News article, blog post"
|
|
- type: "Product"
|
|
description: "Product information with reviews/pricing"
|
|
- type: "FAQPage"
|
|
description: "Frequently asked questions"
|
|
- type: "LocalBusiness"
|
|
description: "Local business information"
|
|
- type: "Event"
|
|
description: "Event information"
|
|
- type: "Recipe"
|
|
description: "Recipe with ingredients and instructions"
|
|
- type: "HowTo"
|
|
description: "Step-by-step instructions"
|
|
- type: "VideoObject"
|
|
description: "Video content metadata"
|
|
|
|
- id: "seo-json-ld-valid"
|
|
name: "JSON-LD Validity"
|
|
description: "JSON-LD structured data should be valid"
|
|
severity: "minor"
|
|
category: "structured_data"
|
|
standard: "Schema.org"
|
|
check_type: "structured_data_check"
|
|
details:
|
|
checks:
|
|
- id: "valid-json"
|
|
description: "JSON-LD must be valid JSON"
|
|
- id: "has-context"
|
|
description: "Must include @context: https://schema.org"
|
|
- id: "has-type"
|
|
description: "Must include @type property"
|
|
- id: "required-properties"
|
|
description: "Must include required properties for the type"
|
|
|
|
# ============================================================
|
|
# 5. Social Media / Open Graph
|
|
# ============================================================
|
|
- id: "seo-open-graph"
|
|
name: "Open Graph Tags"
|
|
description: "Page should have Open Graph meta tags for social sharing"
|
|
severity: "minor"
|
|
category: "social"
|
|
standard: "Open Graph Protocol"
|
|
check_type: "meta_tag_check"
|
|
details:
|
|
required_tags:
|
|
- property: "og:title"
|
|
description: "Title for social sharing"
|
|
- property: "og:description"
|
|
description: "Description for social sharing"
|
|
- property: "og:image"
|
|
description: "Image for social sharing (min 1200x630px recommended)"
|
|
- property: "og:url"
|
|
description: "Canonical URL for social sharing"
|
|
- property: "og:type"
|
|
description: "Content type (website, article, etc.)"
|
|
recommended_tags:
|
|
- property: "og:site_name"
|
|
description: "Website name"
|
|
- property: "og:locale"
|
|
description: "Locale for the content"
|
|
|
|
- id: "seo-twitter-cards"
|
|
name: "Twitter Card Tags"
|
|
description: "Page should have Twitter Card meta tags"
|
|
severity: "info"
|
|
category: "social"
|
|
standard: "Twitter Cards"
|
|
check_type: "meta_tag_check"
|
|
details:
|
|
tags:
|
|
- name: "twitter:card"
|
|
description: "Card type (summary, summary_large_image, player)"
|
|
required: true
|
|
- name: "twitter:title"
|
|
description: "Title for Twitter sharing"
|
|
required: false
|
|
- name: "twitter:description"
|
|
description: "Description for Twitter sharing"
|
|
required: false
|
|
- name: "twitter:image"
|
|
description: "Image for Twitter sharing"
|
|
required: false
|
|
|
|
# ============================================================
|
|
# 6. Crawling & Indexing
|
|
# ============================================================
|
|
- id: "seo-crawlability"
|
|
name: "Page Crawlability"
|
|
description: "Page should be crawlable by search engines"
|
|
severity: "critical"
|
|
category: "crawling"
|
|
standard: "Google Search Central"
|
|
check_type: "crawl_check"
|
|
details:
|
|
checks:
|
|
- id: "status-200"
|
|
description: "Page should return HTTP 200 status"
|
|
- id: "not-blocked-robots"
|
|
description: "Page should not be blocked by robots.txt"
|
|
- id: "not-noindex"
|
|
description: "Page should not have noindex directive (unless intended)"
|
|
- id: "content-type-html"
|
|
description: "Content-Type should be text/html"
|
|
|
|
- id: "seo-favicon"
|
|
name: "Favicon"
|
|
description: "Site should have a favicon"
|
|
severity: "info"
|
|
category: "technical"
|
|
standard: "Google Search Central"
|
|
check_type: "file_check"
|
|
details:
|
|
description: "Favicons appear in search results and browser tabs"
|
|
check_locations:
|
|
- '<link rel="icon">'
|
|
- '<link rel="shortcut icon">'
|
|
- "/favicon.ico"
|
|
|
|
- id: "seo-404-page"
|
|
name: "Custom 404 Page"
|
|
description: "Site should have a custom 404 error page"
|
|
severity: "minor"
|
|
category: "technical"
|
|
standard: "Google Search Essentials"
|
|
check_type: "http_check"
|
|
details:
|
|
description: "Custom 404 pages help users navigate back to working pages"
|
|
|
|
- id: "seo-nofollow-usage"
|
|
name: "Nofollow Link Usage"
|
|
description: "Check for proper use of rel=nofollow on links"
|
|
severity: "info"
|
|
category: "links"
|
|
standard: "Google Search Central - Links"
|
|
check_type: "link_check"
|
|
details:
|
|
rel_values:
|
|
- value: "nofollow"
|
|
description: "Do not follow this link"
|
|
use_case: "User-generated content, untrusted links"
|
|
- value: "ugc"
|
|
description: "User-generated content"
|
|
use_case: "Comments, forum posts"
|
|
- value: "sponsored"
|
|
description: "Paid/sponsored link"
|
|
use_case: "Advertisements, sponsored content"
|