@roottale/cms-renderer-next 0.10.0 → 0.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,19 @@
1
1
  # @roottale/cms-renderer-next
2
2
 
3
+ ## 0.11.0
4
+
5
+ ### Minor Changes
6
+
7
+ - `@roottale/cms-renderer-next/routes` 추가 — 외부 Next.js 사이트의 공통 라우트를
8
+ 1줄 팩토리로 표준화. 블로그 조회와 동일한 단일 `ROOTTALE_API_KEY` 사용.
9
+ - `createFeedRoute(config)` — `app/feed.xml/route.ts` GET (RSS 2.0). `fetchPosts`
10
+ 실패를 swallow → 라우트가 _항상 200_(빌드 시 정적 생성 throw 로 인한 404 방지).
11
+ - `createSitemap(config, staticRoutes)` — `app/sitemap.ts` default export.
12
+ - `createRevalidateRoute({ apiKey, revalidate, alsoRevalidate? })` —
13
+ `app/api/revalidate/route.ts` POST. admin(mysite.roottale.com)의 글/카테고리/미디어
14
+ 변경 서명 webhook 검증 후 본문 paths + 파생 경로(feed·sitemap·블로그 인덱스)를
15
+ 모두 revalidate → 각 사이트 자동 반영. `revalidatePath` 주입(패키지가 next 비의존).
16
+
3
17
  ## 0.10.0
4
18
 
5
19
  ### Patch Changes
@@ -0,0 +1,101 @@
1
+ /**
2
+ * `@roottale/cms-renderer-next/routes` — Next.js App Router 통합 라우트 팩토리.
3
+ *
4
+ * 외부 고객사 사이트가 매번 직접 작성하던 공통 라우트(RSS feed / sitemap /
5
+ * revalidate webhook)를 1줄 팩토리로 표준화. 블로그 조회와 *동일한* 단일
6
+ * customer API key(`ROOTTALE_API_KEY`)를 사용한다.
7
+ *
8
+ * 설계 원칙:
9
+ * - feed/sitemap 은 `fetchPosts` 실패를 swallow → 라우트가 *항상 200* (빌드 시
10
+ * 정적 생성이 throw 해서 404 나는 사고 방지).
11
+ * - revalidate 는 admin(mysite.roottale.com)에서 글/카테고리/미디어 변경 시 보내는
12
+ * 서명 webhook 을 검증하고, 본문 paths + 파생 경로(feed·sitemap·블로그 인덱스)를
13
+ * 모두 revalidate → 각 사이트에 자동 반영.
14
+ * - `next` 에 직접 의존하지 않음: revalidate 콜백(`revalidatePath`)을 주입받는다.
15
+ *
16
+ * 사용 예는 roottale-docs `services/external-site-integration.md` 참조.
17
+ */
18
+ interface RootTaleSiteConfig {
19
+ /** `rtlk_cust_*` — 블로그 조회와 같은 키. 서버에서만(절대 브라우저 금지). */
20
+ apiKey: string;
21
+ /** API base 오버라이드. 기본 `https://api.roottale.com`. */
22
+ apiBase?: string;
23
+ /** 사이트 절대 URL (뒤 슬래시 없이). 예: `https://kjmtax.roottale.app`. */
24
+ siteUrl: string;
25
+ /** 피드/사이트 제목. */
26
+ title: string;
27
+ description?: string;
28
+ /** 기본 "ko-KR". */
29
+ language?: string;
30
+ /** 블로그 base path. 기본 "/blog". */
31
+ blogBasePath?: string;
32
+ }
33
+ /**
34
+ * `app/feed.xml/route.ts` 용 GET 핸들러. RSS 2.0.
35
+ *
36
+ * ```ts
37
+ * import { createFeedRoute } from "@roottale/cms-renderer-next/routes";
38
+ * export const dynamic = "force-dynamic";
39
+ * export const GET = createFeedRoute({
40
+ * apiKey: process.env.ROOTTALE_API_KEY!,
41
+ * siteUrl: "https://example.com",
42
+ * title: "Example 블로그",
43
+ * description: "...",
44
+ * });
45
+ * ```
46
+ */
47
+ declare function createFeedRoute(config: RootTaleSiteConfig): () => Promise<Response>;
48
+ /** Next `MetadataRoute.Sitemap[number]` 와 구조 호환(타입 결합 회피). */
49
+ interface SitemapEntry {
50
+ url: string;
51
+ lastModified?: string | Date;
52
+ changeFrequency?: "always" | "hourly" | "daily" | "weekly" | "monthly" | "yearly" | "never";
53
+ priority?: number;
54
+ }
55
+ /**
56
+ * `app/sitemap.ts` default export. 정적 경로 + 글 경로를 합친다.
57
+ *
58
+ * ```ts
59
+ * import { createSitemap } from "@roottale/cms-renderer-next/routes";
60
+ * export default createSitemap(
61
+ * { apiKey: process.env.ROOTTALE_API_KEY!, siteUrl: "https://example.com", title: "..." },
62
+ * [{ url: "https://example.com", changeFrequency: "weekly", priority: 1 }],
63
+ * );
64
+ * ```
65
+ */
66
+ declare function createSitemap(config: RootTaleSiteConfig, staticRoutes?: SitemapEntry[]): () => Promise<SitemapEntry[]>;
67
+ interface RevalidateRouteConfig {
68
+ /** `rtlk_cust_*` — 블로그 조회/webhook 검증 공통 키. */
69
+ apiKey: string;
70
+ apiBase?: string;
71
+ /** Next 의 `revalidatePath` (또는 동등 콜백) 주입. */
72
+ revalidate: (path: string) => void | Promise<void>;
73
+ /** 블로그 base path. 기본 "/blog". */
74
+ blogBasePath?: string;
75
+ /**
76
+ * 모든 글/분류/미디어 변경에 대해 *항상 함께* revalidate 할 파생 경로.
77
+ * 피드·사이트맵·블로그 인덱스처럼 "글 목록"에 의존하는 경로 — admin 변경이
78
+ * 자동 반영되려면 필수. 기본: ["/feed.xml", "/sitemap.xml", blogBase].
79
+ */
80
+ alsoRevalidate?: string[];
81
+ /** (옵션) jti replay 방지 hook (cms-client verify 로 전달). */
82
+ consumeJti?: (jti: string, exp: number) => Promise<"new" | "seen">;
83
+ }
84
+ /**
85
+ * `app/api/revalidate/route.ts` 용 POST 핸들러.
86
+ *
87
+ * admin(mysite.roottale.com)이 글/카테고리/미디어 변경 시 보내는 서명 webhook 을
88
+ * 검증하고, 본문 paths + 파생 경로(feed·sitemap·블로그 인덱스)를 revalidate.
89
+ *
90
+ * ```ts
91
+ * import { revalidatePath } from "next/cache";
92
+ * import { createRevalidateRoute } from "@roottale/cms-renderer-next/routes";
93
+ * export const POST = createRevalidateRoute({
94
+ * apiKey: process.env.ROOTTALE_API_KEY!,
95
+ * revalidate: revalidatePath,
96
+ * });
97
+ * ```
98
+ */
99
+ declare function createRevalidateRoute(config: RevalidateRouteConfig): (req: Request) => Promise<Response>;
100
+
101
+ export { type RevalidateRouteConfig, type RootTaleSiteConfig, type SitemapEntry, createFeedRoute, createRevalidateRoute, createSitemap };
package/dist/routes.js ADDED
@@ -0,0 +1,110 @@
1
+ // src/routes.ts
2
+ import {
3
+ fetchPosts,
4
+ generateRssXml,
5
+ rssItemsFromPosts
6
+ } from "@roottale/cms-client/server";
7
+ import {
8
+ verifyRootTaleWebhook
9
+ } from "@roottale/cms-client/webhook";
10
+ function trimSlash(u) {
11
+ return u.replace(/\/+$/, "");
12
+ }
13
+ async function safeFetchPosts(config, limit) {
14
+ try {
15
+ const page = await fetchPosts({
16
+ apiKey: config.apiKey,
17
+ baseUrl: config.apiBase,
18
+ type: "post",
19
+ limit
20
+ });
21
+ return page.items;
22
+ } catch (err) {
23
+ console.error("[@roottale/cms-renderer-next/routes] fetchPosts \uC2E4\uD328:", err);
24
+ return [];
25
+ }
26
+ }
27
+ function createFeedRoute(config) {
28
+ return async function GET() {
29
+ const siteUrl = trimSlash(config.siteUrl);
30
+ const blogBase = config.blogBasePath ?? "/blog";
31
+ const posts = await safeFetchPosts(config, 50);
32
+ const xml = generateRssXml({
33
+ title: config.title,
34
+ link: `${siteUrl}${blogBase}`,
35
+ feedUrl: `${siteUrl}/feed.xml`,
36
+ description: config.description,
37
+ language: config.language,
38
+ items: rssItemsFromPosts(posts, {
39
+ postUrl: (p) => `${siteUrl}${blogBase}/${p.slug}`
40
+ })
41
+ });
42
+ return new Response(xml, {
43
+ headers: {
44
+ "content-type": "application/rss+xml; charset=utf-8",
45
+ "cache-control": "public, max-age=1800, s-maxage=1800"
46
+ }
47
+ });
48
+ };
49
+ }
50
+ function createSitemap(config, staticRoutes = []) {
51
+ return async function sitemap() {
52
+ const siteUrl = trimSlash(config.siteUrl);
53
+ const blogBase = config.blogBasePath ?? "/blog";
54
+ const posts = await safeFetchPosts(config, 200);
55
+ const postEntries = posts.map((p) => ({
56
+ url: `${siteUrl}${blogBase}/${p.slug}`,
57
+ lastModified: p.updatedAt ?? p.publishedAt ?? void 0,
58
+ changeFrequency: "weekly",
59
+ priority: 0.6
60
+ }));
61
+ return [...staticRoutes, ...postEntries];
62
+ };
63
+ }
64
+ function pathsFromBody(rawBody) {
65
+ try {
66
+ const body = JSON.parse(rawBody);
67
+ if (Array.isArray(body.paths)) {
68
+ return body.paths.filter((p) => typeof p === "string");
69
+ }
70
+ } catch {
71
+ }
72
+ return [];
73
+ }
74
+ function createRevalidateRoute(config) {
75
+ const blogBase = config.blogBasePath ?? "/blog";
76
+ const alsoRevalidate = config.alsoRevalidate ?? [
77
+ "/feed.xml",
78
+ "/sitemap.xml",
79
+ blogBase
80
+ ];
81
+ return async function POST(req) {
82
+ const rawBody = await req.text();
83
+ const result = await verifyRootTaleWebhook({
84
+ rawBody,
85
+ headers: req.headers,
86
+ apiKey: config.apiKey,
87
+ apiBase: config.apiBase,
88
+ consumeJti: config.consumeJti
89
+ });
90
+ if (!result.ok) {
91
+ return Response.json({ ok: false, reason: result.reason }, { status: 401 });
92
+ }
93
+ const paths = [.../* @__PURE__ */ new Set([...pathsFromBody(rawBody), ...alsoRevalidate])];
94
+ for (const p of paths) {
95
+ await config.revalidate(p);
96
+ }
97
+ return Response.json({
98
+ ok: true,
99
+ event: result.event,
100
+ deliveryId: result.deliveryId,
101
+ revalidated: paths
102
+ });
103
+ };
104
+ }
105
+ export {
106
+ createFeedRoute,
107
+ createRevalidateRoute,
108
+ createSitemap
109
+ };
110
+ //# sourceMappingURL=routes.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/routes.ts"],"sourcesContent":["/**\n * `@roottale/cms-renderer-next/routes` — Next.js App Router 통합 라우트 팩토리.\n *\n * 외부 고객사 사이트가 매번 직접 작성하던 공통 라우트(RSS feed / sitemap /\n * revalidate webhook)를 1줄 팩토리로 표준화. 블로그 조회와 *동일한* 단일\n * customer API key(`ROOTTALE_API_KEY`)를 사용한다.\n *\n * 설계 원칙:\n * - feed/sitemap 은 `fetchPosts` 실패를 swallow → 라우트가 *항상 200* (빌드 시\n * 정적 생성이 throw 해서 404 나는 사고 방지).\n * - revalidate 는 admin(mysite.roottale.com)에서 글/카테고리/미디어 변경 시 보내는\n * 서명 webhook 을 검증하고, 본문 paths + 파생 경로(feed·sitemap·블로그 인덱스)를\n * 모두 revalidate → 각 사이트에 자동 반영.\n * - `next` 에 직접 의존하지 않음: revalidate 콜백(`revalidatePath`)을 주입받는다.\n *\n * 사용 예는 roottale-docs `services/external-site-integration.md` 참조.\n */\n\nimport {\n fetchPosts,\n generateRssXml,\n rssItemsFromPosts,\n type CmsPostContent,\n} from \"@roottale/cms-client/server\";\nimport {\n verifyRootTaleWebhook,\n type VerifyRootTaleWebhookResult,\n} from \"@roottale/cms-client/webhook\";\n\nexport interface RootTaleSiteConfig {\n /** `rtlk_cust_*` — 블로그 조회와 같은 키. 서버에서만(절대 브라우저 금지). */\n apiKey: string;\n /** API base 오버라이드. 기본 `https://api.roottale.com`. */\n apiBase?: string;\n /** 사이트 절대 URL (뒤 슬래시 없이). 예: `https://kjmtax.roottale.app`. */\n siteUrl: string;\n /** 피드/사이트 제목. */\n title: string;\n description?: string;\n /** 기본 \"ko-KR\". */\n language?: string;\n /** 블로그 base path. 기본 \"/blog\". */\n blogBasePath?: string;\n}\n\nfunction trimSlash(u: string): string {\n return u.replace(/\\/+$/, \"\");\n}\n\nasync function safeFetchPosts(\n config: RootTaleSiteConfig,\n limit: number,\n): Promise<CmsPostContent[]> {\n try {\n const page = await fetchPosts({\n apiKey: config.apiKey,\n baseUrl: config.apiBase,\n type: \"post\",\n limit,\n });\n return page.items;\n } catch (err) {\n // 빌드/런타임 fetch 실패 시 빈 목록 — 라우트는 항상 정상 응답(404 방지).\n console.error(\"[@roottale/cms-renderer-next/routes] fetchPosts 실패:\", err);\n return [];\n }\n}\n\n/**\n * `app/feed.xml/route.ts` 용 GET 핸들러. RSS 2.0.\n *\n * ```ts\n * import { createFeedRoute } from \"@roottale/cms-renderer-next/routes\";\n * export const dynamic = \"force-dynamic\";\n * export const GET = createFeedRoute({\n * apiKey: process.env.ROOTTALE_API_KEY!,\n * siteUrl: \"https://example.com\",\n * title: \"Example 블로그\",\n * description: \"...\",\n * });\n * ```\n */\nexport function createFeedRoute(\n config: RootTaleSiteConfig,\n): () => Promise<Response> {\n return async function GET(): Promise<Response> {\n const siteUrl = trimSlash(config.siteUrl);\n const blogBase = config.blogBasePath ?? \"/blog\";\n const posts = await safeFetchPosts(config, 50);\n const xml = generateRssXml({\n title: config.title,\n link: `${siteUrl}${blogBase}`,\n feedUrl: `${siteUrl}/feed.xml`,\n description: config.description,\n language: config.language,\n items: rssItemsFromPosts(posts, {\n postUrl: (p) => `${siteUrl}${blogBase}/${p.slug}`,\n }),\n });\n return new Response(xml, {\n headers: {\n \"content-type\": \"application/rss+xml; charset=utf-8\",\n \"cache-control\": \"public, max-age=1800, s-maxage=1800\",\n },\n });\n };\n}\n\n/** Next `MetadataRoute.Sitemap[number]` 와 구조 호환(타입 결합 회피). */\nexport interface SitemapEntry {\n url: string;\n lastModified?: string | Date;\n changeFrequency?:\n | \"always\"\n | \"hourly\"\n | \"daily\"\n | \"weekly\"\n | \"monthly\"\n | \"yearly\"\n | \"never\";\n priority?: number;\n}\n\n/**\n * `app/sitemap.ts` default export. 정적 경로 + 글 경로를 합친다.\n *\n * ```ts\n * import { createSitemap } from \"@roottale/cms-renderer-next/routes\";\n * export default createSitemap(\n * { apiKey: process.env.ROOTTALE_API_KEY!, siteUrl: \"https://example.com\", title: \"...\" },\n * [{ url: \"https://example.com\", changeFrequency: \"weekly\", priority: 1 }],\n * );\n * ```\n */\nexport function createSitemap(\n config: RootTaleSiteConfig,\n staticRoutes: SitemapEntry[] = [],\n): () => Promise<SitemapEntry[]> {\n return async function sitemap(): Promise<SitemapEntry[]> {\n const siteUrl = trimSlash(config.siteUrl);\n const blogBase = config.blogBasePath ?? \"/blog\";\n const posts = await safeFetchPosts(config, 200);\n const postEntries: SitemapEntry[] = posts.map((p) => ({\n url: `${siteUrl}${blogBase}/${p.slug}`,\n lastModified: p.updatedAt ?? p.publishedAt ?? undefined,\n changeFrequency: \"weekly\",\n priority: 0.6,\n }));\n return [...staticRoutes, ...postEntries];\n };\n}\n\nexport interface RevalidateRouteConfig {\n /** `rtlk_cust_*` — 블로그 조회/webhook 검증 공통 키. */\n apiKey: string;\n apiBase?: string;\n /** Next 의 `revalidatePath` (또는 동등 콜백) 주입. */\n revalidate: (path: string) => void | Promise<void>;\n /** 블로그 base path. 기본 \"/blog\". */\n blogBasePath?: string;\n /**\n * 모든 글/분류/미디어 변경에 대해 *항상 함께* revalidate 할 파생 경로.\n * 피드·사이트맵·블로그 인덱스처럼 \"글 목록\"에 의존하는 경로 — admin 변경이\n * 자동 반영되려면 필수. 기본: [\"/feed.xml\", \"/sitemap.xml\", blogBase].\n */\n alsoRevalidate?: string[];\n /** (옵션) jti replay 방지 hook (cms-client verify 로 전달). */\n consumeJti?: (jti: string, exp: number) => Promise<\"new\" | \"seen\">;\n}\n\n/** webhook 본문에서 paths[] 추출(서명 검증 후 호출 — body_hash 가 binding). */\nfunction pathsFromBody(rawBody: string): string[] {\n try {\n const body = JSON.parse(rawBody) as { paths?: unknown };\n if (Array.isArray(body.paths)) {\n return body.paths.filter((p): p is string => typeof p === \"string\");\n }\n } catch {\n /* 비-JSON 본문 — 파생 경로만 revalidate */\n }\n return [];\n}\n\n/**\n * `app/api/revalidate/route.ts` 용 POST 핸들러.\n *\n * admin(mysite.roottale.com)이 글/카테고리/미디어 변경 시 보내는 서명 webhook 을\n * 검증하고, 본문 paths + 파생 경로(feed·sitemap·블로그 인덱스)를 revalidate.\n *\n * ```ts\n * import { revalidatePath } from \"next/cache\";\n * import { createRevalidateRoute } from \"@roottale/cms-renderer-next/routes\";\n * export const POST = createRevalidateRoute({\n * apiKey: process.env.ROOTTALE_API_KEY!,\n * revalidate: revalidatePath,\n * });\n * ```\n */\nexport function createRevalidateRoute(\n config: RevalidateRouteConfig,\n): (req: Request) => Promise<Response> {\n const blogBase = config.blogBasePath ?? \"/blog\";\n const alsoRevalidate = config.alsoRevalidate ?? [\n \"/feed.xml\",\n \"/sitemap.xml\",\n blogBase,\n ];\n\n return async function POST(req: Request): Promise<Response> {\n const rawBody = await req.text();\n const result: VerifyRootTaleWebhookResult = await verifyRootTaleWebhook({\n rawBody,\n headers: req.headers,\n apiKey: config.apiKey,\n apiBase: config.apiBase,\n consumeJti: config.consumeJti,\n });\n if (!result.ok) {\n return Response.json({ ok: false, reason: result.reason }, { status: 401 });\n }\n\n // 본문 paths + 파생 경로(항상) — 중복 제거.\n const paths = [...new Set([...pathsFromBody(rawBody), ...alsoRevalidate])];\n for (const p of paths) {\n await config.revalidate(p);\n }\n\n return Response.json({\n ok: true,\n event: result.event,\n deliveryId: result.deliveryId,\n revalidated: paths,\n });\n };\n}\n"],"mappings":"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","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@roottale/cms-renderer-next",
3
- "version": "0.10.0",
3
+ "version": "0.11.0",
4
4
  "type": "module",
5
5
  "description": "RootTale CMS public-render React/Next.js Server Components. SSR-only RSC components (RootTaleBlogList / RootTaleBlogPost / RootTaleLeadForm) for external customer sites. Companion of @roottale/cms-renderer-astro (ADR-0034 §1.5 amended).",
6
6
  "main": "./dist/index.js",
@@ -16,6 +16,11 @@
16
16
  "import": "./dist/server.js",
17
17
  "default": "./dist/server.js"
18
18
  },
19
+ "./routes": {
20
+ "types": "./dist/routes.d.ts",
21
+ "import": "./dist/routes.js",
22
+ "default": "./dist/routes.js"
23
+ },
19
24
  "./styles": "./dist/cms-public.css",
20
25
  "./package.json": "./package.json"
21
26
  },