Back to articles

배포 파이프라인에 SEO 감사를 넣기: API, 종료 코드, 그리고 CI 레시피

SEOReport Team·
apici-cdautomationgithub-actionstechnical-seodevops

CI에서 사이트를 감시하는 작동 레시피: 세 개의 REST 엔드포인트, 빌드 게이트가 읽을 수 있는 점수 필드, 실제 회귀와 인프라 블립의 차이.

가장 가시성에 큰 비용을 주는 기술적 회귀는 코드 리뷰에서 보이지 않는다. 잘못된 호스트를 가리키는 canonical 태그, 자기 참조를 잃은 hreflang 블록, 리버스 프록시 설정에서 삭제된 Content-Security-Policy 헤더, 하이드레이션 뒤로 콘텐츠를 이동하는 라우트 — 이 모든 것들은 차이(diff)에서 괜찮아 보이고 당신이 가진 모든 테스트를 통과한다. 그들은 몇 주 후, 트래픽 차트에서, 그들을 야기한 커밋이 스크롤되어 보이지 않게 된 뒤에 나타난다.

해결책은 구조적이다: 누군가 기억할 때가 아니라 배포 시 감사를 실행한다. 모든 SEOReport 리포트는 REST을 통해 제공되며, 응답은 숫자 점수 블록을 담고 있고, 셸 스크립트는 그 블록을 종료 코드로 변환할 수 있다. 이것이 전체 메커니즘이다.

기계가 제때 잡아내는 네 가지 회귀 클래스

네 가지 실패 모드는 인간 리뷰를 특히 잘 통과하며, 그것들을 구체적으로 명명할 가치가 있다. 왜냐하면 그것들이 실제로 게이트가 보호하는 것이기 때문이다.

Hreflang. 클러스터는 자기 참조, 반환 링크, 합리적인 x-default, 잘 형성된 URL, 그리고 색인 가능하며 자기 자신으로 canonical을 지정하는 대체 페이지에 의존한다. CMS 마이그레이션이 URL 패턴을 재작성하면 모든 로케일에서 반환 링크가 동시에 끊어질 수 있으며, 개별 로케일에서는 깨진 것처럼 보이지 않는다.

Canonicalization. canonical은 존재해야 하고, 리다이렉트하지 않는 색인 가능한 대상을 가리켜야 하며, 사이트 자체 호스트에 머물러야 한다. 이 실패의 최악 버전은 프로덕션에 배포된 스테이징 canonical — 당신이 색인하고 싶지 않은 호스트를 조용히 지명하는 페이지이다.

Security headers. Content-Security-Policy, HSTS, Referrer-Policy, X-Frame-Options, X-Content-Type-Options, 그리고 Permissions-Policy. 이들은 인프라 구성에 존재하며, 애플리케이션 코드가 아니라, 그래서 프록시나 엣지-컨피그 변경 중에 사라지고 아무도 눈치채지 못한다.

Render parity. 페이지는 HTML 응답에서 한 버전을, 스크립트가 실행된 뒤에 다른 버전을 전달할 수 있다: 다른 타이틀, 누락된 헤딩, 늦게 주입된 canonical, 또는 하이드레이션 이후에만 존재하는 본문 텍스트. 우리 게시된 render-parity data가 판결을 내렸을 때, 대부분의 사이트는 HTML를 읽는 모든 사용자에게 실질적으로 더 얇은 페이지를 제공하고 있었습니다. 구성 파일에 한 개의 ssr: false만 있으면 사이트가 그 그룹으로 이동합니다.

네 가지 모두 결정적이며, 네 가지 모두 검사하기에 저렴하고, 네 가지 모두 사람이 이미 의심스러울 때만 검사하는 종류의 것입니다.

세 개의 엔드포인트와 하나의 루프

파이프라인이 필요로 하는 API 표면은 작습니다. API 접근은 유료 플랜에 포함되며 베어러 토큰으로 인증됩니다: 대시보드에서 키를 발급받아 Authorization: Bearer sr__live_your_api_key로 보냅니다. 같은 키가 REST과 MCP를 엽니다; 전체 참조는 개발자 페이지에 있습니다.

graph TD A[배포가 완료됩니다] --> B["POST /api/v1/reports"] B --> C["GET /api/v1/reports/:id/ready"] C -->|"ready: false"| C C -->|"error.code present"| D[인프라 신호, 종료 75] C -->|"ready: true"| E["GET /api/v1/reports/:id"] E --> F{예산 내에서 점수 블록?} F -->|예| G[종료 0] F -->|아니오| H[종료 1, 빌드 실패]

보고서를 제출하는 것은 하나의 POST입니다:

bash
JOB_ID=$(curl -sS -X POST https://seoreport.dev/api/v1/reports \
-H "Authorization: Bearer $SEOREPORT_API_KEY" \
-H "Content-Type: application/json" \
-d '{"url": "https://example.com", "forceRerun": true}' \
| jq -r '.report.jobId')

CI에서 특히 그 본문에 관한 두 가지가 중요합니다.

forceRerun는 API가 사용 가능한 스냅샷을 재사용할 때 존재합니다 — 버튼을 클릭하는 사람에게는 합리적이지만, 배포 게이트에는 부적절하며, 그렇지 않으면 이전 릴리스를 평가합니다. 응답은 submission.reusedSnapshot에서 어떤 일이 일어났는지 알려줍니다. 강제 새 실행은 유료 계정 기능입니다; 그 권한이 없는 키는 이유를 명명하는 403를 받으며, 조용히 오래된 데이터를 반환하지 않습니다.

감사 범위는 URL 경로에서 유추됩니다. 베어 오리진은 전체 사이트 감사를 실행합니다; 경로가 있는 URL 은 해당 페이지를 독립적으로 감시합니다. 따라서 파이프라인은 모든 배포에서 홈페이지를 차단하고, 변경이 접촉한 특정 경로를 추가할 수 있습니다, 추가 매개변수 없이.

그 다음 폴링. GET /api/v1/reports/:id/ready 은 저렴한 상태 엔드포인트입니다 — ready, status, stage, pollAfterMs 을 반환하며, 실행이 실패하면 error 객체를 반환합니다:

bash
for _ in $(seq 1 60); do
READY=$(curl -sS -H "Authorization: Bearer $SEOREPORT_API_KEY" \
"https://seoreport.dev/api/v1/reports/$JOB_ID/ready")
CODE=$(echo "$READY" | jq -r '.error.code // empty')
if [ -n "$CODE" ]; then
echo "audit did not complete: $CODE"
exit 75
fi
[ "$(echo "$READY" | jq -r '.ready')" = "true" ] && break
sleep 5
done

그 exit 75 은 의도적이며, 대부분의 통합이 잘못 파악하는 부분입니다.

회귀와 발생하지 않은 실행을 구분합니다

error.code 은 기계가 읽을 수 있는 이유와 retryable 불리언을 담고 있습니다. DNS_FAILURE, CRAWLER_BLOCKED, REDIRECT_LOOP, TLS_ERROR, NO_CONTENT, STALE_ENGINE_VERSION 은 재시도 불가로 표시됩니다, 재시도하면 동일한 답을 반환하기 때문입니다. 그 목록 대부분은 사이트에 대한 조건을 설명하며, 몇 가지 항목 — 리다이렉트 루프, TLS 오류, 이제 크롤러를 차단하는 봇 규칙 — 은 실제 배포 결함으로 빌드를 실패시킬 가치가 있습니다. 그 세트 외부의 코드는 일시적이며 다시 시도할 가치가 있습니다.

"사이트가 회귀했다"와 "감사가 실행되지 않았다"를 같은 빨간 빌드로 결합하는 게이트는 한 달 이내에 비활성화됩니다. 종료 코드에서 분리하십시오: 게이트가 강제하도록 요청한 판결은 1 , 완전히 판결이 없었던 실행은 75 — 전통적인 EX_TEMPFAIL — 입니다. 대부분의 CI 시스템은 두 번째 실행을 재시도하고 첫 번째 실행은 사람에게 전달하도록 구성할 수 있습니다.

점수 블록 읽기

ready 이 true 가 되면 GET /api/v1/reports/:id 가 보고서 객체를 반환합니다. score 블록은 게이트가 읽는 부분입니다:

bash
REPORT=$(curl -sS -H "Authorization: Bearer $SEOREPORT_API_KEY" \
"https://seoreport.dev/api/v1/reports/$JOB_ID")
echo "$REPORT" | jq '.report.score
| {overall, totalChecks, passedChecks, failedChecks,
warnChecks, inconclusiveChecks, failingCriticalChecks}'

그 필드 중 세 개가 대부분의 신호를 전달합니다.

failingCriticalChecks 은 엔진이 비판적으로 분류한 실패 수입니다 — 인덱스에서 페이지를 제거하거나 크롤러가 콘텐츠를 완전히 숨길 수 있는 경우입니다. 첫 번째 게이트의 경우, 이 숫자만 필요하며 > 0 은 첫날에 합리적인 임계값입니다.

overall 은 복합 점수이며, 랙셋으로 유용합니다: 이전 배포의 값을 저장하고 새 값이 선택한 허용 오차보다 더 떨어지면 실패합니다. 이는 단일 검사에서 표시되지 않는 느린 침식도 포착합니다.

domainScores 은 점수를 seo, ai, performance, security, brand 로 나누며, 각각 자체 pass, fail, warn 카운트를 가집니다. 도메인별 바닥값은 서로 다른 팀이 서로 다른 예산을 소유하도록 하며 — 보안 점수는 엣지 구성을 담당하는 사람에게 의미 있는 게이트이며, 콘텐츠 팀이 배포한 것과는 무관합니다.

inconclusiveChecks 은 자체 규칙이 필요합니다: 절대로 게이트에 사용하지 마십시오. 체크는 엔진이 확실한 결론에 도달하지 못했을 때 결론이 불확정이라고 보고하고, 이를 실패로 처리하면 모두가 게이트를 무시하도록 훈련됩니다.

점수 블록은 보고서 요약의 일부이므로 빌드 게이트는 전체 결과를 가져오지 않고도 이를 읽을 수 있습니다. 보관용 전체 페이로드를 원할 때 GET /api/v1/reports/:id/result은 전체 결과를 반환하고, GET /api/v1/reports/:id/download?format=json은 동일한 객체를 다운로드 가능한 아티팩트로 반환합니다 — 두 배포 사이의 차이를 나중에 검사할 수 있도록 빌드에 첨부할 가치가 있습니다.

워크플로에 연결하기

위 내용은 CI-벤더 전용이 아닙니다. GitHub Actions에서 전체 게이트는 이미 가지고 있는 스크립트를 호출하는 하나의 단계입니다:

yaml
- name: SEO audit gate
env:
SEOREPORT_API_KEY: ${{ secrets.SEOREPORT_API_KEY }}
AUDIT_URL: https://example.com
run: ./scripts/seo-gate.sh

seo-gate.sh이 제출, 폴링, 그리고 결정을 내립니다:

bash
CRITICAL=$(echo "$REPORT" | jq -r '.report.score.failingCriticalChecks // 0')
SECURITY=$(echo "$REPORT" | jq -r \
'.report.score.domainScores[] | select(.domain == "security") | .score')
if [ "$CRITICAL" -gt 0 ]; then
echo "::error::$CRITICAL critical checks failing"
exit 1
fi
echo "clean — security domain at $SECURITY"

배포가 끝난 후에 실행하고, 미리보기 환경에 대해 실행하지 마십시오. 미리보기 URL은 일반적으로 기본 인증 또는 봇 챌린지 뒤에 있으며, 페이지를 가져올 수 없는 감사는 점수가 아니라 CRAWLER_BLOCKED을 보고합니다. 실제 출처에 대한 배포 후 감사는 더 간단하고 크롤러가 경험하는 것에 더 가깝습니다.

인접한 두 모양을 아는 것은 가치가 있습니다. 귀하의 오케스트레이션이 이미 스크래핑 플랫폼에 있다면, 우리의 Apify actor는 이 모든 연결 없이 일정에 따라 동일한 엔진을 실행합니다. 그리고 에이전트의 경우, 동일한 베어러 키가 https://seoreport.dev/mcp에서 MCP 연결을 인증하며, 보고서 도구는 connect에서 광고됩니다 — 트래픽 감소를 조사하는 에이전트는 감사를 실행하고 결과를 직접 읽을 수 있으며, 이는 개발자 페이지의 REST 표면과 함께 문서화되어 있습니다.

게이트가 다루지 않는 것

배포 게이트는 한 가지 질문에 답합니다: 이 릴리스가 회귀를 도입했는가. 배포 없이 변경되는 모든 것에 대해 무음입니다 — 만료되는 인증서, 대시보드에서 편집된 CDN 구성, 렌더링을 차단하기 시작하는 서드파티 스크립트, 귀하의 정규 표현과 충돌을 변경하는 경쟁사의 마이그레이션. 도메인별 모니터링은 게이트의 항상 켜진 보완입니다: SEO 모니터링 API는 동일한 계정과 동일한 키로 매주 등록된 도메인을 재감사합니다.

좁은 버전으로 시작하십시오. 하나의 엔드포인트, 하나의 필드, failingCriticalChecks > 0, 그리고 감사를 전혀 실행할 수 없었을 때의 정직한 exit 75. 연간 두 번 트리거되고 두 번 모두 신뢰받는 게이트는 모든 사람이 건너뛰도록 배웠던 포괄적인 게이트보다 가치가 있습니다.

사이트의 완전한 진단을 받으세요

근거 기반 보고서와 우선순위 실행 계획을 월간 크레딧 요금제로 받으세요.