birchholt 직접 해보고 남기는 기록

배포는 성공했는데, 없는 주소가 200을 돌려주고 있었습니다

글 세 편을 미리 써두고 날짜를 벌려서 하나씩 열리게 해뒀습니다. push는 오늘 했지만 공개일은 내일부터라, 지금은 아직 안 보이는 게 정상입니다.

그게 맞는지 확인하려고 주소를 하나 찍어봤습니다.

/posts/hugo-over-astro/  →  200

200이 나왔습니다. 안 열려야 하는데 열립니다.

처음 의심한 것, 전부 아니었습니다

미래 날짜 처리가 안 먹은 줄 알았습니다. 로컬에서 다시 확인해봤습니다.

$ hugo                 → public/posts/ 에 그 글 없음
$ hugo --buildFuture   → public/posts/ 에 그 글 있음

로컬은 정상입니다. 빌드 결과물에 애초에 그 파일이 없습니다. 그런데 라이브에서는 200이 나옵니다. 없는 파일이 어떻게 200이 되는지가 이해가 안 됐습니다.

sitemap을 봤습니다. 새 글 세 편이 없습니다. 배포된 결과물은 제대로였다는 뜻입니다.

여기서 없는 파일이 200을 돌려준다는 게 이상하다는 걸 뒤늦게 알아챘습니다. 그래서 확인 방향을 바꿨습니다.

대조군을 넣어보니 답이 나왔습니다

제가 확인하던 것은 “이 주소가 200인가"였습니다. 그 질문에는 비교할 대상이 없었습니다. 그래서 존재할 리 없는 주소를 하나 만들어 같이 찍었습니다.

/posts/hugo-over-astro/      →  200
/posts/이런글은없다-zzz/       →  200      ← 대조군

대조군도 200입니다. 게다가 둘 다 홈 내용을 돌려주고 있었습니다.

$ curl -s https://birchholt.com/posts/이런글은없다-zzz/ | grep -oE '<title>[^<]*'
<title>birchholt — 직접 해보고 남기는 기록

새 글 문제가 아니었습니다. 사이트 전체가 그렇게 동작하고 있었습니다. 아무 주소나 치면 홈이 200으로 나옵니다.

제가 놓친 지점은 여기입니다. “200이 나온다"를 “페이지가 있다"로 읽었습니다. 둘은 다른 사실입니다. 대조군 없이 한 URL만 찍는 건 확인이 아니라 짐작입니다. 없는 주소를 같이 찍었으면 처음 5초에 알았을 일이었습니다.

원인 — 파일 하나가 없었습니다

빌드 결과물을 봤습니다.

$ ls public/
ads.txt  css  index.html  index.xml  posts  privacy  robots.txt  sitemap.xml

404.html이 없습니다.

Cloudflare Pages는 요청한 경로에 파일이 없으면 404.html을 찾습니다. 그것도 없으면 index.html을 200으로 내줍니다. 단일 페이지 앱을 위한 동작인데, 정적 블로그에서는 없는 주소 전부가 홈 복사본이 됩니다.

Hugo는 layouts/404.html이 있어야 public/404.html을 만듭니다. 저는 그걸 안 만들어뒀습니다.

로컬에서 안 보인 이유도 여기 있습니다. hugo server는 없는 주소에 자체 404 화면을 띄웁니다. 로컬에서는 멀쩡해 보이고, 라이브에서만 다르게 동작합니다.

고치면서 걸린 것 둘

1. Hugo가 영문 제목을 자동으로 넣습니다.

layouts/404.html을 만들고 빌드했더니 이렇게 나왔습니다.

<title>404 Page not found — birchholt</title>

본문은 한국어인데 제목만 영어입니다. Hugo가 넣는 기본값입니다.

2. content/404.md로 제목을 바꾸려다 더 나빠졌습니다.

제목을 바꾸려고 content/404.mdtitle을 적어봤습니다. 제목은 안 바뀌었고, 대신 sitemap에 이게 생겼습니다.

<loc>https://birchholt.com/404/</loc>

404 페이지를 색인해달라고 sitemap에 올린 꼴입니다. 고치려다 없던 문제를 만들었습니다. 파일을 지우고 원래대로 돌렸습니다.

제목은 <head>를 만드는 쪽에서 처리했습니다. 겸사겸사 404에는 색인 거부를 붙이고 canonical은 빼도록 했습니다 — 없는 페이지가 자기 자신을 정본이라고 가리킬 이유가 없습니다.

{{- $pageTitle := cond (eq .Kind "404") "주소를 찾지 못했습니다" .Title -}}

확인을 자동화에 넣었습니다

같은 걸 또 놓치지 않으려고 배포 워크플로에 두 줄을 넣었습니다.

빌드 결과에 파일이 있는지:

test -f public/404.html || { echo "🔴 404.html 이 없다" >&2; exit 1; }

그리고 라이브에서 진짜 404가 나오는지:

miss="$(curl -s -o /dev/null -w '%{http_code}' "https://birchholt.com/__deploy_check_404__/")"
[ "$miss" = "404" ] || { echo "🔴 없는 주소가 404 가 아니다 ($miss)" >&2; exit 1; }

두 번째가 진짜입니다. 파일이 있는 것과 서버가 그 파일을 404로 내주는 것은 다른 사실이고, 이번에 틀린 건 두 번째 쪽이었습니다.

원래 있던 라이브 검증은 //privacy//ads.txt가 200인지만 봤습니다. 있는 페이지가 열리는지만 확인하고, 없는 페이지가 안 열리는지는 확인하지 않았습니다. 검사 항목이 한쪽 방향만 보고 있었습니다.

정리