Next.js의 `app/sitemap.ts`를 오랜만에 다시 봤는데, 코드가 대략 이런 모양이었다.
export default function sitemap(): MetadataRoute.Sitemap {
const now = new Date()
return [
{ url: `${BASE_URL}/`, lastModified: now, ... },
{ url: `${BASE_URL}/tools`, lastModified: now, ... },
{ url: `${BASE_URL}/classes`, lastModified: now, ... },
// ...도구 페이지 전체도 동일
]
}
정적 페이지와 도구 페이지의 `lastModified`에 전부 `now`를 박아 두고 있었다. 바꾼 적도 없는 `/`, `/tools`, `/classes`가 sitemap 요청이 들어올 때마다 "방금 수정됨"으로 나가고 있었다는 뜻이다.
이게 왜 문제일까?
`lastmod`는 sitemaps.org 스펙에 정의된 "페이지가 마지막으로 수정된 시각" 을 가리키는 필드다. 크롤러가 이 값을 참고해 "이 URL은 이전에 본 버전과 달라졌는지"를 판단한다.
구글은 `lastmod`에 대해 비교적 구체적으로 입장을 밝혀 두었다. Googlebot은 `lastmod`가 정확하고 일관되게 제공될 때만 이 값을 신뢰한다. 페이지가 실제로는 바뀌지 않았는데 `lastmod`만 계속 갱신되면, 구글은 해당 사이트의 `lastmod` 값 자체를 점점 무시한다.
즉, 모든 URL에 현재 시각을 찍어 보내는 것은 "매번 다 바뀌었어요"라고 외치는 것과 같고, 그 결과는 더 자주 크롤링되는 것이 아니라 `lastmod` 신호 자체가 버려지는 것이다. 자주 업데이트되는 가이드 페이지의 진짜 변경 신호까지 같이 묻히게 된다는 뜻이다. 내 경우 더 나쁜 점은, sitemap이 요청 시점에 생성되다 보니 같은 배포 버전 안에서도 요청마다 `lastmod`가 달랐다는 것이다.
크롤러가 어제 받은 sitemap과 오늘 받은 sitemap을 비교했을 때 모든 URL의 `lastmod`가 바뀌어 있다. 이건 신호가 아니라 노이즈다.
검색엔진은 sitemap을 어떻게 쓰는가
오해하기 쉬운 부분을 정리해 두면 다음과 같다.
- sitemap은 크롤링 대상 URL의 후보 목록을 알려주는 힌트다. 랭킹에 직접 영향을 주는 도구가 아니다.
- `lastmod`는 크롤러가 재방문 스케줄을 짤 때 참고하는 신호다. 바뀌었다고 표시된 URL을 우선적으로 다시 보는 식이다.
- 단, 이 참고는 신뢰가 쌓였을 때만 이루어진다. 신뢰가 깨지면 해당 사이트의 `lastmod`는 통째로 무시된다.
- `changefreq`, `priority`는 구글이 공식적으로 사용하지 않는다고 명시한 필드다. 넣어도 무해하지만 의미 있는 효과는 기대하지 않는 것이 맞다.
결국 sitemap에서 우리가 통제할 수 있는 유의미한 신호는 사실상 URL 목록과 정확한 `lastmod` 두 가지뿐이라고 봐도 크게 틀리지 않는다. 그 둘 중 하나를 스스로 망가뜨리고 있었던 셈이다.
문제 해결
원칙은 단순하다. "실제로 바뀌었을 때만 `lastmod`를 바꾼다."
- 정적 페이지(`/`, `/tools`, `/classes` 등): 콘텐츠가 코드에 박혀 있으므로, 해당 페이지를 실제로 수정한 배포 날짜를 상수로 고정한다.
- 원래부터 DB의 `updatedAt`을 쓰고 있었고, 이건 그대로 두었다. 이게 원래 `lastmod`가 쓰여야 할 올바른 형태다.
// src/lib/sitemap-metadata.ts
export const STATIC_PAGE_LAST_MODIFIED = new Date('2026-04-17')
export const TOOL_LAST_MODIFIED: Record = {
'damage-calculator': new Date('2026-03-22'),
'gem-simulator': new Date('2026-04-10'),
// ...
}
`lastModified: new Date()` 는 코드만 보면 자연스럽고 "항상 최신"처럼 보이지만, sitemap의 맥락에서는 "이 필드의 의미를 스스로 파괴하는 코드" 였다.
