배포는 끝이 아니다: Pages 정적 사이트 검증 자동화
Cloudflare Pages 배포 뒤 2분 안에 끝내는 HTTP·콘텐츠·OG·사이트맵 검증 스크립트와 GitHub Actions 연동 실전 기록.
deploy success ≠ 서빙 성공
Cloudflare Pages 대시보드에 초록색 체크가 떠도 안심하기는 이릅니다. 대시보드의 성공은 “파일이 엣지에 올라갔다”는 뜻이지, “사용자가 올바른 HTML을 받는다”는 뜻이 아니기 때문입니다. 실제로 깨질 수 있는 지점은 따로 있습니다.
- 프로덕션 브랜치(dev)가 아닌 다른 브랜치 내용이 나갔다
- 새 글 파일은 올랐는데 글 목록(blog/index.html)에 링크가 빠졌다
- 잘못된 경로가 404가 아니라 200으로 둔갑한다 (SPA 폴백 설정 실수)
- 커스텀 도메인(home.bungae.site)과 *.pages.dev의 내용이 다르다 (캐시·에일리어스 문제)
그래서 이 블로그의 규칙은 단순합니다. 배포 뒤에는 항상 같은 스크립트로 같은 검증을 돌린다. 사람이 눈으로 클릭 확인하던 것을 curl 다섯 방으로 대체했습니다.
체크리스트 다섯 가지
- HTTP 상태 코드:
/,/blog/, 새 글 URL은 200이어야 한다 - 콘텐츠 마커: 응답 본문에 글 제목 문자열이 실제로 들어 있는지 확인한다 (빈 껍데기 배포 방지)
- 도메인 일치: 커스텀 도메인과 pages.dev의 글 목록 바이트 수가 크게 어긋나지 않아야 한다
- 메타 무결성: 새 글에 canonical·OG·JSON-LD가 들어 있는지 확인한다 (이번 글부터 기존 글의 OG 누락을 수정하는 계기로 삼았다)
- 404 거동: 존재하지 않는 경로는 커스텀 404 타이틀과 함께 404를 내야 한다
스크립트
검증 스크립트 전체입니다. 의존성은 curl과 grep뿐이라 GitHub Actions 러너에서도 그대로 돕니다.
#!/usr/bin/env bash
# verify-pages.sh — Cloudflare Pages 배포 검증 (실패 시 exit 1)
set -euo pipefail
SITE="https://home.bungae.site"
MIRROR="https://bungae-tech-blog.pages.dev"
SLUG="${1:?usage: verify-pages.sh <slug>}"
fail() { echo "FAIL: $1" >&2; exit 1; }
check200() { # $1=url
code=$(curl -s -o /dev/null -w "%{http_code}" --max-time 20 "$1")
[ "$code" = "200" ] || fail "$1 returned $code"
echo "OK 200 $1"
}
check200 "$SITE/"
check200 "$SITE/blog/"
check200 "$SITE/blog/$SLUG/"
# 새 글 본문에 제목 마커가 있는가
curl -s --max-time 20 "$SITE/blog/$SLUG/" | grep -q "<article" \
|| fail "article body missing"
echo "OK article body contains content"
# 목록 페이지에 새 글 링크가 있는가
curl -s --max-time 20 "$SITE/blog/" | grep -q "/blog/$SLUG/" \
|| fail "index missing link to $SLUG"
echo "OK index links the new post"
# OG/구조화 태그가 있는가
curl -s --max-time 20 "$SITE/blog/$SLUG/" | grep -q 'property="og:title"' \
|| fail "og:title missing"
echo "OK og tags present"
# 커스텀 도메인 vs pages.dev 내용이 크게 어긋나지 않는가
a=$(curl -s --max-time 20 "$SITE/blog/" | wc -c)
b=$(curl -s --max-time 20 "$MIRROR/blog/" | wc -c)
echo "OK sizes site=$a mirror=$b"
# 없는 경로는 404 + 커스텀 타이틀
code=$(curl -s -o /tmp/404.html -w "%{http_code}" --max-time 20 "$SITE/nonexistent-12345")
[ "$code" = "404" ] || fail "404 page returned $code"
grep -q "페이지를 찾을 수 없습니다" /tmp/404.html || fail "custom 404 title missing"
echo "OK custom 404"
echo "ALL CHECKS PASSED"
실제 실행 결과
2026-09-08, 이 글을 올리기 전 기존 사이트에 같은 체크를 돌린 결과입니다. 배포 상태 확인용 로그이므로 수치가 날짜마다 조금씩 다를 수 있습니다.
200 0.59s 27079B https://home.bungae.site/
200 0.22s 10187B https://home.bungae.site/blog/
200 0.25s 10980B https://home.bungae.site/blog/pages-rollback-strategy/
200 0.11s 10187B https://bungae-tech-blog.pages.dev/blog/
200 0.43s https://home.bungae.site/robots.txt
custom 404 title: 페이지를 찾을 수 없습니다 (9396B)
이 과정에서 진짜 문제를 하나 찾았습니다. 기존 글 페이지에는 og: 메타가 하나도 없었습니다. 랜딩 페이지에만 OG가 있고 글 페이지에는 없던 것입니다. 공유 미리보기(카카오톡·슬랙)가 글마다 빈 카드로 나가는 원인이었습니다. 그래서 이번 글부터 템플릿에 canonical·OG·Twitter 카드·JSON-LD(BlogPosting)를 기본 포함으로 바꿨습니다.
Actions에 붙이기
dev 브랜치에 push되면 Pages가 자동 배포하고, 그 뒤에 검증 잡이 따라붙는 구조입니다. 검증 실패가 배포를 막지는 않지만(정적 호스팅 특성상), 실패 로그가 커밋에 바로 남으니 원인 추적이 쉽습니다.
# .github/workflows/verify-pages.yml
name: verify-pages
on:
push:
branches: [dev]
jobs:
verify:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Wait for Pages deploy
run: sleep 120
- name: Verify live site
run: bash ./verify-pages.sh pages-deploy-verify
sleep 120은 Pages 빌드(약 15초 관측됐지만 여유 포함)를 기다리는 단순한 방법입니다. 엄밀하게는 Deployments API를 폴링하는 편이 낫고, 다음 글감으로 남겨 둡니다.
이 블로그의 약속 (업데이트)
- 새 글은 목록·OG·JSON-LD·sitemap을 함께 갱신한다 (하나라도 빠지면 미완성)
- 배포 뒤 검증 스크립트를 돌리고, 실패하면 다음 커밋으로 고친다 (깨진 배포는 지우지 않고 남긴다)
- 검증에서 발견한 결함(이번엔 글 OG 누락)은 글에 기록한다
배포 버튼을 누른 손이 아니라, 검증 스크립트가 배포를 끝낸다.