UNPKG

@roottale/cms-mcp

Version:

RootTale CMS integration MCP server — bundled integration docs, Next.js example code, and public API lookup tools. Run with: npx @roottale/cms-mcp

130 lines (100 loc) 4.88 kB
--- title: 발행 웹훅 (캐시 자동 갱신) description: 글 발행/수정 시 사이트 캐시를 near-real-time으로 갱신하는 웹훅 설정 --- # 발행 웹훅 — 캐시 자동 갱신 어드민에서 글을 발행/수정/삭제하면 RootTale이 고객 사이트의 revalidate 엔드포인트로 **ES256 서명된 웹훅**을 보냅니다. 사이트는 서명을 검증하고 `revalidatePath`를 호출해 즉시 갱신합니다. - 별도 webhook secret을 보관할 필요가 없습니다 — 검증은 사이트 스코프 API 키로 JWKS 공개키를 가져와 수행합니다. - ISR `revalidate = 1800` 같은 시간 기반 설정은 **fallback**입니다. 정상 경로는 웹훅입니다. ## 1. revalidate 라우트 추가 (Next.js) ```ts // 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` 콜백을 확장합니다: ```ts 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 등록 1. 어드민 **내 사이트 > (사이트 선택)** 페이지로 이동 2. "글 발행 후 사이트 자동 갱신" 카드에서: - **자동 갱신 URL**: `https://<사이트 도메인>/api/revalidate` - **활성화** 체크 3. 저장 — ES256 키페어가 자동 발급됩니다 (고객 측 보관 항목 없음) URL을 비우고 저장하면 웹훅이 비활성화됩니다. ## 웹훅 발송 트리거 - 게시물 생성/발행/수정/삭제/발행 취소 - 게시물의 카테고리·태그 변경 (블로그 카드의 카테고리 라벨이 바뀌므로) - 분류(taxonomy) 용어 삭제 (해당 용어를 참조하던 발행 글 전부) - 블로그 표시 설정 변경 (TOC, 작성자/발행일, 작성자 카드) - 디자인 토큰 변경 - 수동 revalidation API 호출 ## 캐시 무효화 규칙 수신 측은 다음을 보장해야 합니다 (`createRevalidateRoute`가 기본 처리): - 모든 서명된 이벤트에서 블로그 목록(`/blog`) revalidate — 글 본문이 안 바뀌어도 카드 메타(카테고리 라벨 등)가 바뀔 수 있음 - 상세 페이지는 현재 slug + payload의 `paths` 힌트 경로 모두 revalidate - 홈에 최신 글 섹션이 있으면 `alsoRevalidate``/` 포함 ## 저수준 검증 — verifyRootTaleWebhook `createRevalidateRoute`를 못 쓰는 환경(다른 프레임워크 등)은 `@roottale/cms-client/webhook`으로 직접 검증합니다: ```ts 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 배포 직후 등 강제 갱신이 필요할 때: ```http 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`·동적 경로 콜백 누락 |