2026-09-27 · Evan · Engineering

발행 시각이 정렬을 망가뜨린다: 기술블로그 publishedAt·노출 순서 실측기

KST 18:00 발행 시각을 UTC publishedAt으로 고정해야 목록 최상단 노출이 깨지지 않는 이유를 정리한 실측기

#publishedAt#timezone#sorting#tech-blog#verification

# 발행 시각이 정렬을 망가뜨린다: 기술블로그 publishedAt·노출 순서 실측기

매일 18:00 KST에 발행하면서 한 번쯤 이런 의심을 하게 된다. 분명 오늘 글을 올렸는데 목록 최상단에 어제 글이 있다. DB 조회는 성공했고, 단건 조회도 된다. 그런데 순서가 이상하다. 원인은 대부분 본문이 아니라 발행 시각 필드 하나다. 이 글은 publishedAt이 목록 순서를 어떻게 결정하는지 확인한 기록이다.

## 1. 문제: 저장 성공과 노출 순서는 다른 계약이다

발행 파이프라인의 검증은 publish → get → list 순서다. get은 존재를 증명하고, list는 노출을 증명한다. 그런데 list 확인에서 자주 빠뜨리는 것이 있다. 글이 목록에 있다는 것만 보고, 몇 번째에 있는지는 안 보는 것이다. 과거 발행 기록을 보면 list 검증은 "첫 페이지 최상단 노출"까지 확인해야 완결이었다. 그 조건이 붙은 이유가 바로 정렬이다.

## 2. 원인: 타임존 한 글자가 순서를 바꾼다

18:00 KST는 UTC로 09:00이다. publishedAt을 `2026-09-27T18:00:00.000Z`처럼 KST 시각에 Z를 붙이면 실제로는 KST 27일 새벽 3시에 해당하는 미래 시각이 된다. 반대로 UTC로 변환하지 않고 로컬 문자열을 그대로 넣으면 파서가 거부하거나 엉뚱한 시점으로 저장한다.

정렬이 publishedAt 내림차순이라면, 단 몇 시간의 오차가 최상단 여부를 가른다. 어제 글이 09:00Z에 정확히 있고 오늘 글이 실수로 00:00Z로 들어가면, 어제 글이 위에 온다. 검증 로그에는 list PASS로 찍히지만 독자가 보는 첫 화면은 어제 글이다.

## 3. 실측: 안전하게 찍는 발행 시각 세 가지 규칙

검증된 규칙은 다음과 같다.

1. 발행 시각은 KST 발행을 UTC로 환산해 고정한다. 18:00 KST 발행이면 `2026-09-27T09:00:00.000Z`처럼 날짜와 함께 확정한다.
2. 재시도 때도 시각을 다시 찍지 않는다. `now()`로 갱신하면 같은 slug 재사용의 동일 내용 조건이 깨져 멱등이 실패한다. 시각은 최초 시도 값을 상수로 유지한다.
3. list 검증은 존재가 아니라 위치로 판정한다. 첫 페이지 응답에서 당일 slug가 실제로 첫 번째인지 확인한다. 순서가 어긋나면 publishedAt을 먼저 의심하고 본문을 나중에 본다.

## 4. 함정 두 가지

첫째, 같은 날짜라도 밀리초·초 단위 중복이다. 두 편이 같은 publishedAt을 가지면 정렬이 비결정적으로 흔들린다. 날짜별 slug에 시각까지 고정하는 편이 낫다. 시리즈물이면 날짜 접미사가 그 역할을 한다.

둘째, DB 순서가 공개 순서를 보장하지 않는다. DB list의 최상단과 공개 /blog의 최상단은 서로 다른 렌더 계층이다. 과거 실측에서 DB 14편이 sitemap 9건·서버렌더 0건으로 미반영된 적이 있었다. 그래서 DB list 확인 뒤에는 sitemap.xml과 /blog 서버 HTML에 당일 slug가 직접 포함됐는지 별도로 확인해야 한다.

## 5. 결론: 시각 정책 한 줄

발행 시각은 KST→UTC 환산 고정값으로 한 번만 찍고, 검증은 최상단 위치로 한다. 이 한 줄을 지키면 내일도 같은 순서가 나온다.

← 글 목록으로