정적 블로그 발행 전, 무엇을 테스트로 막았나

정적 블로그에서 깨진 내부 링크, 잘못 연결된 언어 대체 링크, 생성에 실패한 OG 이미지를 발행 전에 확인하도록 만든 테스트와 빌드 검사 기록입니다.

정적 블로그는 서버가 없어서 단순해 보인다. 실제로 이 저장소에서 pnpm build가 성공해도, Markdown 안의 상대 경로·언어별 URL·생성된 메타 태그가 최종 HTML에서 모두 맞는다는 보장은 없었다.

그래서 내부 링크 검사 스크립트, alternates.ts, og-image.ts에 걸쳐 확인 범위를 나눴다. 실제 회귀 조건은 internal-links.test.ts, alternates.test.ts, article-seo-structure.test.ts에 남겼다. 본문 링크 하나가 404로 가거나, 번역본이 없는데 대체 링크가 만들어지거나, 제목이 긴 OG 이미지가 깨지는 문제를 각각 다른 실패로 다뤘다.

이 글에서 구분한 것은 기계가 잡을 수 있는 실패와 사람이 읽어야 하는 판단이다. 링크·언어 대체 링크·OG 이미지의 생성 규칙은 자동화했고, 본문이 유용한지와 공유 카드가 보기 좋은지는 수동 검토로 남겼다. 테스트가 초록색이어도 글의 품질까지 확인된 것은 아니기 때문이다.

발행 실패를 세 종류로 나눴다

처음에는 pnpm build가 성공하면 충분하다고 생각하기 쉽다. 하지만 빌드 성공은 파일을 만들었다는 뜻이지, 독자가 따라갈 모든 경로와 언어 관계가 맞다는 뜻은 아니다.

이 사이트에서는 발행 전 실패를 다음 세 종류로 나눴다.

실패 독자에게 생기는 문제 발행 전 확인
내부 링크 오류 관련 글이나 이미지가 404로 간다 빌드된 HTML의 내부 URL을 실제 산출물과 대조한다
언어 대체 링크 오류 없는 번역본으로 이동하거나 언어 관계 신호가 흐려진다 translationKey와 언어 조합을 검사한다
OG 이미지 오류 공유 카드의 제목·이미지 정보가 깨지거나 엉뚱해진다 생성 URL, XML 이스케이프, 긴 제목 규칙을 테스트한다

각 항목은 검색 유입만을 위한 장치가 아니다. 링크는 독자가 다음 글로 이동하는 경로이고, 언어 전환은 번역본이 있을 때만 약속해야 하며, OG 이미지는 공유 링크를 받는 사람이 처음 보는 정보다.

Markdown이 아니라 빌드된 HTML에서 링크를 검사한다

내부 링크 검사는 글 원본에서 Markdown 문법만 읽지 않는다. 빌드가 끝난 dist/의 HTML 파일을 모두 읽고, a, img, script, link 같은 태그의 href, src, srcset을 찾는다. 같은 사이트의 URL이면 실제 빌드 산출물에 해당 경로가 있는지 확인한다.

이 순서를 고른 이유는 상대 경로 때문이다. 글 안의 링크가 맞아 보이더라도 최종 HTML의 위치가 바뀌면 ../ 계산이 달라질 수 있다. Markdown 변환 뒤의 이미지 경로나 컴포넌트가 만든 링크도 원본 검사만으로는 놓친다.

예를 들어 이 검사는 다음처럼 결과를 낸다.

/index.html -> /ko/posts/missing/

이 메시지가 하나라도 나오면 빌드 뒤 내부 링크 검사를 실패시킨다. 반대로 외부 사이트 URL은 이 단계에서 검사하지 않는다. 외부 서비스의 일시 장애까지 로컬 발행 검증이 책임지면, 내 글과 무관한 이유로 배포가 불안정해지기 때문이다.

Google도 다른 페이지를 발견하고 이해하는 데 표준 <a href> 링크를 사용한다고 안내한다. 다만 이 검사로 Google 크롤링이나 순위를 보장한다는 뜻은 아니다. 여기서 보장하려는 것은 더 좁다. 공개된 정적 결과물 안에서, 같은 사이트의 링크가 존재하는 경로를 가리킨다는 것이다. Google의 크롤링 가능한 링크 안내

번역본이 없으면 hreflang도 만들지 않는다

한국어 글과 영어 글이 모두 있다고 해서 모든 글이 1:1 번역은 아니다. 원본만 있는 글도 있고, 한쪽 언어에서만 독립적으로 다룰 만한 운영 기록도 있다.

그래서 글마다 translationKey를 두되, 같은 키를 가진 반대 언어 글이 실제로 있을 때만 hreflang 대체 링크를 만든다. 번역본이 없으면 언어 전환 메뉴는 그 언어의 글 목록으로 가고, 존재하지 않는 번역 URL을 만들어 내지 않는다.

한국어 글 + 영어 대응 글 있음
→ ko, en, x-default 대체 링크 생성

한국어 원본만 있음
→ ko 자기 참조와 x-default를 한국어 원본으로 생성
→ 영어 전환은 영어 글 목록으로 이동

이 규칙에서 더 중요한 실패는 중복이다. 같은 언어에서 같은 translationKey를 두 글이 공유하면, 어느 글이 번역본인지 결정할 수 없다. 글 경로를 만들기 전에 이 경우를 오류로 내도록 했다.

Google은 언어별 URL을 쓸 때 hreflang으로 서로의 언어 버전을 명시하라고 권장하며, 각 버전이 자기 자신과 다른 모든 언어 버전을 가리키도록 안내한다. Google의 다국어 페이지 가이드 하지만 원본만 있는 글에 가짜 영어 URL을 붙이라는 뜻은 아니다. 번역 관계가 실제로 있는 글만 연결하는 편이 독자에게도 정직하다.

OG 이미지는 생성 규칙만 자동으로 확인한다

글별 OG 이미지는 별도 파일을 손으로 만들지 않는다. 각 글의 제목·카테고리·언어를 받아 SVG를 만들고, 그 결과 URL을 og:imagetwitter:image에 넣는다.

여기서 자동 테스트가 확인하는 범위는 세 가지다.

확인 막으려는 실패
제목·카테고리·언어가 바뀌면 이미지 버전도 바뀜 바뀐 이미지 내용에 맞는 새 OG URL을 만들지 못하는 문제
&, <, 큰따옴표 같은 XML 문자를 이스케이프 제목 한 줄 때문에 SVG 자체가 깨지는 문제
긴 제목을 최대 세 줄로 자름 이미지 바깥으로 제목이 밀려나는 문제

og:image URL의 버전은 수정일이 아니라 실제 이미지 내용에서 계산한다. 날짜만 바꿔 캐시를 무효화하지 않고, 제목·카테고리·언어 또는 이미지 템플릿이 달라졌을 때만 새 URL이 생기게 하려는 선택이다. 이 URL 변경은 새 미리보기 이미지의 구분을 돕지만, 이미 공유된 카드나 SNS 크롤러 캐시를 즉시 갱신한다고 보장하지는 않는다.

다만 이 테스트가 “이미지가 보기 좋다”를 판정하지는 않는다. 한국어와 영어의 글자 폭, 긴 고유명사, 줄바꿈의 균형, SNS별 카드 렌더링은 결국 실제 이미지로 봐야 한다. 자동 검증은 깨진 SVG와 무한히 긴 제목을 먼저 막고, 최종 디자인 판단을 대신하지 않는다.

검증은 배포보다 먼저 실패해야 한다

이 저장소의 package.jsonGitHub Actions 워크플로는 push와 pull request에서 먼저 pnpm verify를 실행한다. 이 명령에는 포맷 검사, lint, 타입·Astro 검사, 테스트, 정적 빌드, 빌드 결과 내부 링크 검사가 들어 있다. 이 작업이 성공해야만 push에서 배포 작업이 시작된다.

pull request: verify
main push:     verify → deploy → IndexNow

이 순서가 중요한 이유는 404를 배포 뒤에 발견해 고치는 비용이 더 크기 때문이다. 이미 sitemap과 RSS에 새 URL이 포함되고 공유 링크가 퍼진 뒤라면, 단순한 경로 오타도 독자가 마주한 실패가 된다.

이전 글에서 다룬 IndexNow 변경 알림도 이 흐름 뒤에 있다. 배포가 끝난 다음에만 변경 URL을 알리는 이유는, 먼저 공개 결과물이 검증된 상태여야 하기 때문이다.

작은 블로그에 필요한 최소안

처음부터 모든 품질 검사를 만들 필요는 없다. 정적 블로그라면 다음 네 단계가 가장 작은 시작점이다.

1. 빌드가 실패하면 발행하지 않는다.
2. 빌드된 HTML의 같은 사이트 링크를 검사한다.
3. 다국어 글만 hreflang을 만들고, 번역 키 중복을 거부한다.
4. 제목에 특수 문자가 있는 OG 이미지와 아주 긴 제목을 테스트한다.

이 중 2번은 링크가 늘어날수록, 3번은 일부 글만 번역하는 사이트일수록 가치가 커진다. 반대로 단일 언어이고 OG 이미지를 만들지 않는 블로그라면 3번과 4번을 억지로 넣을 이유는 없다.

무엇을 아직 검증하지 못하는가

테스트를 늘리면 모든 문제가 사라질 것처럼 보이지만, 여기에는 의도적으로 남겨 둔 영역이 있다.

아직 자동으로 보장하지 않는 것 확인 방법
Google이 언제 크롤링·색인하는가 Search Console URL 검사와 기간별 관찰
SNS가 이전 OG 이미지를 언제 버리는가 플랫폼별 공유 디버거와 실제 공유 확인
OG 이미지의 시각적 완성도 대표적인 한국어·영어 긴 제목을 렌더링해 검토
외부 링크가 계속 살아 있는가 필요할 때만 별도 링크 점검
본문이 독자에게 충분히 유용한가 출처·실험 기록·독자 피드백을 포함한 편집 검토

이 구분은 중요하다. 빌드가 초록색이라고 글의 품질이나 검색 성과까지 자동으로 통과한 것은 아니다. 앞서 Search Console에 노출은 잡히는데 클릭이 없을 때 볼 것에서 정리했듯, 노출과 클릭은 배포 검증이 끝난 뒤 별도의 운영 데이터로 판단해야 한다.

발행 후에도 남는 확인

정적 블로그의 발행 품질은 CMS가 대신 보장해 주지 않는다. 하지만 모든 것을 수동 체크리스트로 남길 필요도 없다. 경로 존재 여부, 실제 번역 관계, SVG가 깨지지 않는 조건처럼 기계가 분명하게 판단할 수 있는 실패는 테스트로 옮기는 편이 낫다.

이 사이트에서는 링크·언어 대체 링크·OG 이미지의 경계를 먼저 자동화했다. 이 검증이 SEO 순위나 독자 만족도를 보장하지는 않는다. 다만 독자가 열 수 없는 URL, 존재하지 않는 번역본, 깨진 공유 이미지를 발행하는 실수는 배포 전에 줄여 준다. 그 위에서야 글의 내용과 운영 데이터를 제대로 검토할 수 있다.

참고한 문서