TechFeedTechFeed
Next.js

Next.js 사이트 prebuild에서 sitemap·RSS·Atom 같이 갱신하기

npm prebuild에 generate-sitemap·feeds를 묶어 배포 전 검색 피드 일관성을 유지하는 실무 패턴. 로컬·CI 체크리스트 포함.

by

한 줄: Next.js 콘텐츠 사이트는 npm run build 직전 prebuild에서 sitemap·RSS·Atom을 같이 재생성해야 검색·구독 피드가 본문과 어긋나지 않는다.


data/*.jsposts/**/*.js를 추가·수정한 뒤 생성 스크립트를 빼먹으면 네이버 Yeti·구글·RSS 리더가 옛 URL만 본다. package.json에 "prebuild": "node scripts/generate-sitemap.js && node scripts/generate-feeds.js"를 두고, 로컬에서도 커밋 전 npm run prebuild로 public 산출물 mtime·URL 개수를 확인한다. Vercel 빌드는 훅이 돌지만, dev 서버와 수동 확인은 명시 실행이 필요하다.


왜 prebuild에 묶나 | 색인과 본문이 어긋나는 사고

정적 산출 사이트(또는 빌드 타임에 XML을 쓰는 하이브리드)에서 새 글은 보통 posts/·data/에 들어가고, 검색엔진용 목록은 public/sitemap*.xml, 구독용은 public/rss.xml·public/atom.xml에 남는다. 본문만 커밋하고 피드를 안 돌리면 배포는 성공해도 크롤러·리더는 어제 목록을 읽는다.


실무에서 자주 나는 패턴이다.


  • 글 파일은 있고 사이트 맵에는 slug가 없음 → 내부 링크로만 도달, 검색 유입 지연
  • sitemap은 갱신됐는데 RSS만 옛 날짜 → 구독 독자 이탈
  • news sitemap(최근 30일)만 빠져 최신 글 신호가 약함
  • 로컬에선 수동 생성 파일을 보고, CI는 옛 public을 그대로 올림

prebuild는 npm 라이프사이클 훅이라 npm run build 앞에 자동 실행된다. “빌드하면 피드도 따라온다”는 팀 규칙을 코드로 고정하는 장치다. Next.js 라우팅·기능 자체는 Next.js 15 새 기능 가이드 쪽을 보면 되고, 이 글은 검색용 정적 피드 동기화만 다룬다.


권장 스크립트 구성 | package.json과 생성기

최소 구성은 두 스크립트를 한 줄로 잇는 것이다. news sitemap이 있으면 같은 훅에 포함한다.


// package.json (발췌)
{
  "scripts": {
    "generate-sitemap": "node scripts/generate-sitemap.js",
    "generate-feeds": "node scripts/generate-feeds.js",
    "prebuild": "npm run generate-sitemap && npm run generate-feeds",
    "build": "next build"
  }
}

생성기는 보통 data/posts.js(또는 posts 디렉터리 require)를 읽어 다음을 쓴다.


  • public/sitemap.xml 인덱스 + sitemap-posts.xml 등 엔티티별 분할
  • public/sitemap-news.xml (최근 30일, 있는 사이트만)
  • public/rss.xml (RSS 2.0), public/atom.xml (Atom 1.0)
  • 각 URL에 lastmod·changefreq·priority (정책에 맞게)

canonical과 trailing slash 정책이 어긋나면 중복 색인이 난다. Next 설정에 trailingSlash: true를 쓰는 사이트는 sitemap·RSS 안의 모든 링크도 슬래시로 끝나게 맞춘다. OG 이미지 redirect 이슈까지 보면 skipTrailingSlashRedirect: true와 세트로 점검한다. App Router 이전 이슈는 App Router 마이그레이션 글의 라우팅 절과 겹친다.


명령 체크리스트 | 커밋 전·배포 전 확인표

콘텐츠 추가 후 표준 발행 순서를 표로 고정해 두면 실수가 줄어든다.


단계 명령·확인 통과 기준
1. 문법 node --check posts/{id}.js exit 0, SyntaxError 없음
2. 피드 재생성 npm run prebuild 에러 없이 완료, public 파일 mtime 갱신
3. URL 포함 rg "my-new-slug" public/sitemap*.xml public/rss.xml 신규 slug가 sitemap·RSS에 존재
4. 개수 sanity URL 개수 vs posts 메타 개수 비교 의도적 제외(noindex 등) 외 일치
5. 커밋 범위 data + posts + public/sitemap* + rss/atom 산출물을 본문과 같은 커밋에 포함
6. 배포 빌드 npm run build (prebuild 자동) 빌드 로그에 generate 스크립트 실행 흔적

# 콘텐츠 수정 직후 (로컬)
node --check posts/3535.js
npm run prebuild

# 산출물 확인
ls -la public/sitemap.xml public/rss.xml public/atom.xml
rg -n "nextjs-prebuild-sitemap-rss-2026" public/sitemap*.xml public/rss.xml public/atom.xml

# 배포 파이프라인에서는 build만 호출해도 prebuild가 선행
npm run build

CI와 로컬 | Vercel만 믿으면 생기는 구멍

호스팅(Vercel 등)에서 npm run build를 돌리면 prebuild 훅이 따라와 서버 산출물은 최신일 수 있다. 그래도 로컬 검증·리뷰 PR·“sitemap.xml 직접 열어보기” 시나리오에서는 명시 실행이 필요하다.


  • 로컬 dev: next dev만 켜면 prebuild가 안 돈다. 피드 확인 전에 npm run prebuild.
  • git diff 리뷰: public XML diff가 없으면 생성 누락 신호다. 콘텐츠 PR에 피드 diff를 같이 요구한다.
  • 자동화 포스팅 (launchd·cron·GitHub Actions): 글 파일 write 후 반드시 prebuild 단계를 포함한다. Claude 잡이 본문만 쓰고 끝내면 색인이 lagged 상태가 된다.
  • 캐시: CDN이 XML을 길게 캐시하면 재생성 후에도 옛 목록이 보일 수 있다. Cache-Control·퍼지 정책을 확인한다.

팀 규칙으로 “콘텐츠 커밋 = data/posts + public 피드 동시 커밋”을 권장한다. 빌드 서버만 최신이고 저장소 public은 한 달 전인 상태는, 다른 브랜치·롤백·정적 미러에서 사고 낸다.


robots.txt에서 sitemap 위치를 가리키는지, Yeti·Daumoa Allow가 있는지도 같이 본다. 피드 링크는 _document.js 또는 root layout의 <link rel="alternate" type="application/rss+xml">로 노출한다.


자주 나는 실수 | 빼먹기·trailing slash·news 누락

현장에서 반복되는 실패 모드다.


  1. prebuild 스크립트 미연결: generate 파일은 있는데 package.json에 훅이 없음. build 로그에 node scripts 실행이 안 보인다.
  2. 한 쪽만 실행: sitemap만 돌리고 feeds를 빼먹음. RSS 구독기만 구식.
  3. trailing slash 불일치: 페이지 canonical은 /foo/인데 sitemap은 /foo. 검색엔진이 둘 다 수집하거나 OG 수집이 깨질 수 있다.
  4. draft·noindex를 피드에 포함: 미공개 slug가 외부에 노출. 생성기에 필터 플래그를 둔다.
  5. 날짜 필드 오류: lastmod·RSS pubDate가 하드코딩 고정일이면 크롤러가 갱신을 무시한다. posts 메타의 date를 소스로 쓴다.
  6. 절대 URL 누락: https://tech.example.com/... 대신 상대 경로만 넣으면 일부 리더가 깨진다. 사이트 기본 URL env를 한곳에서 읽는다.

생성기 디버깅 시에는 새 글 하나 slug를 골라 rg로 public 전체를 긁는 방법이 가장 빠르다. 개수가 안 맞으면 필터 조건(카테고리, isTrend, published 플래그)을 로그로 덤프한다.


Next 쪽 데이터 로딩·마이그레이션 이슈와 섞이면 원인 분기가 어려워진다. 라우터·데이터 계층 문제는 별도 글로 분리하고, 피드 생성기는 “메타 배열 → XML 문자열” 순수 변환으로 유지하는 편이 안전하다.


팀 워크플로 | 사람·자동화 공통 파이프라인

권장 발행 파이프라인을 한 줄로 고정한다.


# 1) 본문·메타 추가/수정
#    data/posts.js + posts/{id}.js

# 2) 문법 검사
node --check posts/{id}.js

# 3) 검색·구독 피드 동기화 (필수)
npm run prebuild

# 4) 스테이징
git add data/ posts/ public/sitemap*.xml public/rss.xml public/atom.xml

# 5) 커밋 후 푸시 (배포는 팀 정책에 따름)
git commit -m "post: add {slug} + refresh feeds"
git push

자동화 에이전트(Claude Code headless, GitHub Actions)도 동일하다. 본문 생성 성공 코드 다음에 prebuild를 강제하고, prebuild 실패 시 커밋을 막는다. 인증 만료로 CLI가 죽으면 피드 단계까지 못 가므로, Claude Code 401 인증 복구와 운영을 같이 본다.


관련 자동화 스케줄은 cron 스케줄 가이드, 리밋 시 재개는 리밋·재개 전략을 참고한다. 피드 동기화는 “SEO 장식”이 아니라 배포 산출물의 일부로 취급해야 한다.


※ 프레임워크·호스팅·스크립트 경로는 프로젝트마다 다르다. 스크립트 이름이 다르면 package.json 훅만 맞춰 같은 원칙을 적용하면 된다.


프레임워크·호스팅 설정은 프로젝트마다 다릅니다. package.json 훅 이름과 스크립트 경로만 환경에 맞게 바꾸면 됩니다.


Next.jssitemapRSSAtomprebuild

함께 보면 좋은 문제 해결

EXPLORE / Next.js

이어서 읽어보기

전체 토픽 둘러보기