발행 웹훅 — 캐시 자동 갱신
어드민에서 글을 발행/수정/삭제하면 RootTale이 고객 사이트의 revalidate
엔드포인트로 ES256 서명된 웹훅을 보냅니다. 사이트는 서명을 검증하고
revalidatePath를 호출해 즉시 갱신합니다.
- 별도 webhook secret을 보관할 필요가 없습니다 — 검증은 사이트 스코프 API 키로 JWKS 공개키를 가져와 수행합니다.
- ISR
revalidate = 1800같은 시간 기반 설정은 fallback입니다. 정상 경로는 웹훅입니다.
1. revalidate 라우트 추가 (Next.js)
// app/api/revalidate/route.ts
import { revalidatePath } from "next/cache";
import { createRevalidateRoute } from "@roottale/cms-renderer-next/routes";
export const POST = createRevalidateRoute({
apiKey: process.env.ROOTTALE_API_KEY!,
apiBase: process.env.ROOTTALE_API_BASE,
revalidate: revalidatePath,
// 글 변경 시 함께 갱신할 추가 경로 (기본: /feed.xml, /sitemap.xml, /blog)
alsoRevalidate: ["/feed.xml", "/sitemap.xml", "/blog", "/"],
});
export function GET(): Response {
return new Response("Method Not Allowed", { status: 405 });
}
블로그가 /blog가 아닌 경로면 blogBasePath: "/insights" 옵션을 추가하세요.
카테고리/태그 인덱스 같은 동적 경로가 있다면 revalidate 콜백을 확장합니다:
function revalidateBlogPath(path: string): void {
revalidatePath(path);
if (path === "/blog/categories") revalidatePath("/blog/categories/[category]", "page");
if (path === "/blog/tags") revalidatePath("/blog/tags/[tag]", "page");
}
2. 어드민에 웹훅 URL 등록
- 어드민 내 사이트 > (사이트 선택) 페이지로 이동
- "글 발행 후 사이트 자동 갱신" 카드에서:
- 자동 갱신 URL:
https://<사이트 도메인>/api/revalidate - 활성화 체크
- 자동 갱신 URL:
- 저장 — ES256 키페어가 자동 발급됩니다 (고객 측 보관 항목 없음)
URL을 비우고 저장하면 웹훅이 비활성화됩니다.
웹훅 발송 트리거
- 게시물 생성/발행/수정/삭제/발행 취소
- 게시물의 카테고리·태그 변경 (블로그 카드의 카테고리 라벨이 바뀌므로)
- 분류(taxonomy) 용어 삭제 (해당 용어를 참조하던 발행 글 전부)
- 블로그 표시 설정 변경 (TOC, 작성자/발행일, 작성자 카드)
- 디자인 토큰 변경
- 수동 revalidation API 호출
캐시 무효화 규칙
수신 측은 다음을 보장해야 합니다 (createRevalidateRoute가 기본 처리):
- 모든 서명된 이벤트에서 블로그 목록(
/blog) revalidate — 글 본문이 안 바뀌어도 카드 메타(카테고리 라벨 등)가 바뀔 수 있음 - 상세 페이지는 현재 slug + payload의
paths힌트 경로 모두 revalidate - 홈에 최신 글 섹션이 있으면
alsoRevalidate에/포함
저수준 검증 — verifyRootTaleWebhook
createRevalidateRoute를 못 쓰는 환경(다른 프레임워크 등)은
@roottale/cms-client/webhook으로 직접 검증합니다:
import { verifyRootTaleWebhook } from "@roottale/cms-client/webhook";
export async function POST(request: Request) {
const rawBody = await request.text(); // 반드시 파싱 전 raw로 검증
const result = await verifyRootTaleWebhook({
rawBody,
headers: request.headers,
apiKey: process.env.ROOTTALE_API_KEY!,
});
if (!result.ok) {
return Response.json({ reason: result.reason }, { status: 401 });
}
// result.event: "post.published" | "post.updated" | "post.deleted"
// result.payload.paths: 갱신할 root-relative 경로 배열
return Response.json({ ok: true });
}
실패 reason 값: missing_signature, invalid_signature, expired,
body_hash_mismatch, timestamp_out_of_window, replay_seen 등.
옵션으로 expectedSiteId(멀티 사이트 하드닝), consumeJti(replay 방지 저장소),
timestampWindowSec(기본 300초)을 지정할 수 있습니다.
수동 revalidation API
배포 직후 등 강제 갱신이 필요할 때:
POST https://api.roottale.com/v1/cms/revalidate
Authorization: Bearer rtlk_cust_...
Content-Type: application/json
{
"event": "post.updated",
"paths": ["/blog", "/blog/my-post"],
"slug": "my-post"
}
트러블슈팅
| 증상 | 확인 |
|---|---|
| 발행해도 사이트 미반영 | 어드민의 자동 갱신 URL·활성화 체크, 배포 도메인 일치 여부 |
401 invalid_signature | ROOTTALE_API_KEY가 해당 사이트 스코프 키인지 |
401 timestamp_out_of_window | 서버 시계 동기화 (NTP) |
| 일부 페이지만 갱신 | alsoRevalidate·동적 경로 콜백 누락 |