@roottale/cms-renderer-next 0.48.0 → 0.51.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.
Files changed (36) hide show
  1. package/CHANGELOG.md +56 -0
  2. package/dist/blog-filter-BKCGM0qd.d.ts +29 -0
  3. package/dist/{chunk-6MFHA555.js → chunk-27RT6B3O.js} +1 -1
  4. package/dist/chunk-27RT6B3O.js.map +1 -0
  5. package/dist/chunk-2QU4JU46.js +3927 -0
  6. package/dist/chunk-2QU4JU46.js.map +1 -0
  7. package/dist/chunk-4RQ3SEDL.js +106 -0
  8. package/dist/chunk-4RQ3SEDL.js.map +1 -0
  9. package/dist/{chunk-OMXEU4LS.js → chunk-QYNEOFL7.js} +140 -136
  10. package/dist/chunk-QYNEOFL7.js.map +1 -0
  11. package/dist/concerns-client.d.ts +1 -1
  12. package/dist/expertise-client.d.ts +1 -1
  13. package/dist/expertise-client.js +1 -1
  14. package/dist/hero-carousel-client.js +1 -1
  15. package/dist/routes.d.ts +334 -20
  16. package/dist/routes.js +249 -68
  17. package/dist/routes.js.map +1 -1
  18. package/dist/seo-asset-hosts.d.ts +36 -0
  19. package/dist/seo-asset-hosts.js +391 -0
  20. package/dist/seo-asset-hosts.js.map +1 -0
  21. package/dist/server.d.ts +23 -241
  22. package/dist/server.js +6 -6
  23. package/dist/{site-config-Dicv57oY.d.ts → site-config-DNpvlqA5.d.ts} +201 -201
  24. package/dist/spaces-client.d.ts +1 -1
  25. package/dist/team-expanding-stage-client.d.ts +1 -1
  26. package/dist/team-expanding-stage-client.js +1 -1
  27. package/dist/templates.d.ts +64 -25
  28. package/dist/templates.js +13 -6
  29. package/dist/templates.js.map +1 -1
  30. package/package.json +7 -2
  31. package/dist/chunk-6MFHA555.js.map +0 -1
  32. package/dist/chunk-FZK3EXYZ.js +0 -103
  33. package/dist/chunk-FZK3EXYZ.js.map +0 -1
  34. package/dist/chunk-LIBSVC4M.js +0 -3642
  35. package/dist/chunk-LIBSVC4M.js.map +0 -1
  36. package/dist/chunk-OMXEU4LS.js.map +0 -1
package/dist/routes.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import {
2
- collectCategories,
2
+ collectCategoriesWithCounts,
3
3
  collectCollectionCategories,
4
4
  resolveCollections,
5
5
  resolveCollectionsResult,
@@ -14,20 +14,21 @@ import { CMS_CLIENT_VERSION } from "@roottale/cms-client/server";
14
14
 
15
15
  // src/routes-feed-sitemap.ts
16
16
  import {
17
- fetchAuthors,
18
- fetchBlogSettings,
19
- fetchPosts,
20
17
  generateRssXml,
21
18
  rssItemsFromPosts
22
19
  } from "@roottale/cms-client/server";
23
20
  import {
24
- isInCollectionMode,
25
- resolvePostCollection as resolvePostCollection2,
26
- resolvePostUrl as resolvePostUrl2
21
+ isInCollectionMode as isInCollectionMode2,
22
+ resolvePostCollection as resolvePostCollection3,
23
+ resolvePostUrl as resolvePostUrl3
27
24
  } from "@roottale/cms-core";
28
25
 
29
26
  // src/routes-metadata.ts
30
27
  import { resolvePostPath } from "@roottale/cms-core";
28
+ function resolveRobotsMeta(flags) {
29
+ if (!flags.noindex && !flags.nofollow) return void 0;
30
+ return { index: !flags.noindex, follow: !flags.nofollow };
31
+ }
31
32
  function buildPostMetadata(post, options = {}) {
32
33
  const seo = post.seo ?? void 0;
33
34
  const title = seo?.title || post.title;
@@ -79,9 +80,11 @@ function buildPostMetadata(post, options = {}) {
79
80
  }
80
81
  };
81
82
  }
82
- if (seo?.noindex || seo?.nofollow) {
83
- meta.robots = { index: !seo.noindex, follow: !seo.nofollow };
84
- }
83
+ const robots = resolveRobotsMeta({
84
+ ...seo?.noindex !== void 0 && { noindex: seo.noindex },
85
+ ...seo?.nofollow !== void 0 && { nofollow: seo.nofollow }
86
+ });
87
+ if (robots) meta.robots = robots;
85
88
  if (post.id) meta.other = { "rt:content-id": post.id };
86
89
  const twitter = { card: "summary_large_image", title };
87
90
  if (description) twitter.description = description;
@@ -106,40 +109,133 @@ function buildHreflangLanguages(translations, siteUrl, locales) {
106
109
  return languages;
107
110
  }
108
111
 
109
- // src/routes-feed-sitemap.ts
110
- function createFeedRoute(config) {
111
- return async function GET() {
112
- const siteUrl = trimSlash(config.siteUrl);
113
- const collections = await resolveCollections(config.collections);
114
- const allPosts = await safeFetchPosts(config, 50);
115
- const inCollectionMode = isInCollectionMode(collections);
116
- const blogBase = config.blogBasePath ?? "/blog";
117
- const posts = inCollectionMode ? allPosts.filter((p) => {
118
- const c = resolvePostCollection2(p, collections);
119
- return c ? c.feed === true && c.detail !== false && c.basePath !== "" : false;
120
- }) : allPosts;
121
- const xml = generateRssXml({
122
- title: config.title,
123
- link: inCollectionMode ? siteUrl : `${siteUrl}${blogBase}`,
124
- feedUrl: `${siteUrl}/feed.xml`,
125
- description: config.description,
126
- language: config.language,
127
- items: rssItemsFromPosts(posts, {
128
- postUrl: (p) => inCollectionMode ? resolvePostUrl2(siteUrl, p, collections) ?? `${siteUrl}${blogBase}/${p.slug}` : `${siteUrl}${blogBase}/${p.slug}`
129
- })
130
- });
131
- return new Response(xml, {
132
- headers: {
133
- "content-type": "application/rss+xml; charset=utf-8",
134
- "cache-control": "public, max-age=1800, s-maxage=1800"
135
- }
112
+ // src/archive-index-policy.ts
113
+ var ARCHIVE_POST_COUNT_LIMIT = 200;
114
+ var NOT_FOUND = {
115
+ render: "notFound",
116
+ index: false,
117
+ inSitemap: false
118
+ };
119
+ var NOINDEX = {
120
+ render: "ok",
121
+ index: false,
122
+ inSitemap: false
123
+ };
124
+ var INDEXED = {
125
+ render: "ok",
126
+ index: true,
127
+ inSitemap: true
128
+ };
129
+ var POLICY_NOT_APPLIED = INDEXED;
130
+ function isNonNegativeInteger(value) {
131
+ return Number.isInteger(value) && value >= 0;
132
+ }
133
+ function resolveCategoryPromotion(promotedSlugs, slug) {
134
+ if (!promotedSlugs) return "unknown";
135
+ return promotedSlugs.includes(slug) ? "promoted" : "not-promoted";
136
+ }
137
+ function resolveAuthorPromotion(authorsEnabled) {
138
+ if (authorsEnabled === void 0) return "unknown";
139
+ return authorsEnabled ? "promoted" : "not-promoted";
140
+ }
141
+ function decideArchiveIndex(input) {
142
+ const page = input.page ?? 1;
143
+ if (!isNonNegativeInteger(input.postCount)) return NOINDEX;
144
+ if (!isNonNegativeInteger(page) || page < 1) return NOINDEX;
145
+ if (input.postCount === 0 && input.kind !== "search") return NOT_FOUND;
146
+ if (page >= 2) return NOINDEX;
147
+ if (input.kind !== "category" && input.kind !== "author") return NOINDEX;
148
+ switch (input.promotion) {
149
+ case "promoted":
150
+ return INDEXED;
151
+ case "unknown":
152
+ return POLICY_NOT_APPLIED;
153
+ default:
154
+ return NOINDEX;
155
+ }
156
+ }
157
+ function buildArchiveMetadata(input, options = {}) {
158
+ const decision = decideArchiveIndex(input);
159
+ const meta = { title: input.title };
160
+ if (input.description) meta.description = input.description;
161
+ if (options.siteUrl && options.path) {
162
+ const normalized = options.path.startsWith("/") ? options.path : `/${options.path}`;
163
+ meta.alternates = { canonical: `${trimSlash(options.siteUrl)}${normalized}` };
164
+ }
165
+ const robots = resolveRobotsMeta({
166
+ noindex: !decision.index,
167
+ ...input.nofollow !== void 0 && { nofollow: input.nofollow }
168
+ });
169
+ if (robots) meta.robots = robots;
170
+ return meta;
171
+ }
172
+
173
+ // src/routes-sitemap-entries.ts
174
+ import {
175
+ fetchAuthors,
176
+ fetchBlogSettings,
177
+ fetchPosts as fetchPosts2
178
+ } from "@roottale/cms-client/server";
179
+ import { isInCollectionMode, resolvePostCollection as resolvePostCollection2, resolvePostUrl as resolvePostUrl2 } from "@roottale/cms-core";
180
+
181
+ // src/archive-categories.ts
182
+ import {
183
+ fetchCategoryCounts,
184
+ fetchPosts
185
+ } from "@roottale/cms-client/server";
186
+ async function fetchBlogArchivePosts(config, options = {}) {
187
+ const page = await fetchPosts({
188
+ apiKey: config.apiKey,
189
+ baseUrl: config.apiBase,
190
+ type: "post",
191
+ limit: ARCHIVE_POST_COUNT_LIMIT,
192
+ // 서버측 "미소속 글" 필터 — 페이지(`excludeCollections`)와 같은 정의.
193
+ excludeCollections: true,
194
+ ...options.revalidate !== void 0 && { revalidate: options.revalidate }
195
+ });
196
+ return page.items;
197
+ }
198
+ async function fetchServerCategoryCounts(config, options) {
199
+ try {
200
+ const result = await fetchCategoryCounts({
201
+ apiKey: config.apiKey,
202
+ baseUrl: config.apiBase,
203
+ // 레거시 `/blog` 계열의 모집단 = 미소속 글. 페이지(`excludeCollections`)·
204
+ // 사이트맵과 **같은 서버 판정**을 쓰기 위해 서버 필터로 지정한다.
205
+ excludeCollections: true,
206
+ ...options.revalidate !== void 0 && { revalidate: options.revalidate }
136
207
  });
137
- };
208
+ if (result.truncated) {
209
+ console.warn(
210
+ "[@roottale/cms-renderer-next/routes] \uBD84\uB958 \uC218\uAC00 \uC11C\uBC84 \uC0C1\uD55C\uC744 \uB118\uC5B4 \uC77C\uBD80\uAC00 \uC751\uB2F5\uC5D0\uC11C \uBE60\uC84C\uC2B5\uB2C8\uB2E4(\uC0C9\uC778\uC5D0\uC11C \uB204\uB77D\uB420 \uC218 \uC788\uC74C)."
211
+ );
212
+ }
213
+ return result.categories.filter((category) => category.publishedPostCount > 0).map((category) => ({
214
+ name: category.name,
215
+ slug: category.slug,
216
+ count: category.publishedPostCount
217
+ }));
218
+ } catch (err) {
219
+ console.warn(
220
+ "[@roottale/cms-renderer-next/routes] \uBD84\uB958\uBCC4 \uAE00 \uC218 \uC9D1\uACC4 API \uC2E4\uD328 \u2014 \uCD5C\uADFC \uAE00 \uBC30\uCE58 \uD30C\uC0DD\uC73C\uB85C \uD3F4\uBC31:",
221
+ err
222
+ );
223
+ return null;
224
+ }
225
+ }
226
+ async function fetchBlogArchiveCategories(config, options = {}) {
227
+ const counts = await fetchServerCategoryCounts(config, options);
228
+ if (counts) return counts;
229
+ return collectCategoriesWithCounts(await fetchBlogArchivePosts(config, options));
138
230
  }
231
+
232
+ // src/routes-sitemap-entries.ts
139
233
  var SITEMAP_DEFAULTS = {
140
234
  blogImages: true,
141
235
  locales: [],
142
236
  authors: false
237
+ // promotedCategorySlugs 는 의도적으로 기본값이 없다 — "모름"(undefined)과
238
+ // "승격 0건"([])을 구분해야 색인 정책을 fail-soft 로 적용할 수 있다.
143
239
  };
144
240
  async function safeFetchAuthors(config) {
145
241
  try {
@@ -151,27 +247,36 @@ async function safeFetchAuthors(config) {
151
247
  }
152
248
  async function resolveSitemapSettings(config, opts) {
153
249
  let remote = {};
250
+ let settingsFetchFailed = false;
154
251
  if (!opts.skipRemoteSettings) {
155
252
  try {
156
253
  remote = (await fetchBlogSettings({
157
254
  apiKey: config.apiKey,
158
255
  baseUrl: config.apiBase
159
256
  })).sitemap ?? {};
160
- } catch {
257
+ } catch (err) {
258
+ settingsFetchFailed = true;
259
+ console.error(
260
+ "[@roottale/cms-renderer-next/routes] fetchBlogSettings \uC2E4\uD328(\uAE30\uBCF8\uAC12\uC73C\uB85C \uC9C4\uD589):",
261
+ err
262
+ );
161
263
  }
162
264
  }
163
- return {
265
+ const settings = {
164
266
  blogImages: opts.blogImages ?? remote.blogImages ?? SITEMAP_DEFAULTS.blogImages,
165
267
  locales: opts.locales ?? remote.locales ?? SITEMAP_DEFAULTS.locales,
166
268
  authors: opts.authors ?? remote.authors ?? SITEMAP_DEFAULTS.authors
167
269
  };
270
+ const promoted = opts.promotedCategorySlugs ?? remote.promotedCategorySlugs;
271
+ if (promoted) settings.promotedCategorySlugs = promoted;
272
+ return { settings, settingsFetchFailed };
168
273
  }
169
274
  async function localesWithPublishedContent(config, locales) {
170
275
  if (locales.length === 0) return locales;
171
276
  const checks = await Promise.all(
172
277
  locales.map(async (locale) => {
173
278
  try {
174
- const page = await fetchPosts({
279
+ const page = await fetchPosts2({
175
280
  apiKey: config.apiKey,
176
281
  baseUrl: config.apiBase,
177
282
  type: "post",
@@ -224,84 +329,152 @@ function postEntries(config, siteUrl, posts, collections, settings, defaultLocal
224
329
  return [entry];
225
330
  });
226
331
  }
227
- function categoryEntries(config, siteUrl, posts, collections) {
332
+ async function safeBlogArchiveCategories(config) {
333
+ try {
334
+ return await fetchBlogArchiveCategories(config);
335
+ } catch (err) {
336
+ console.error(
337
+ "[@roottale/cms-renderer-next/routes] \uC544\uCE74\uC774\uBE0C \uCE74\uD14C\uACE0\uB9AC \uC870\uD68C \uC2E4\uD328:",
338
+ err
339
+ );
340
+ return [];
341
+ }
342
+ }
343
+ async function categoryEntries(config, siteUrl, collections, promotedSlugs) {
344
+ const inSitemap = (category) => decideArchiveIndex({
345
+ kind: "category",
346
+ promotion: resolveCategoryPromotion(promotedSlugs, category.slug),
347
+ postCount: category.count
348
+ }).inSitemap;
228
349
  if (isInCollectionMode(collections)) {
350
+ const posts = await safeFetchPosts(config, ARCHIVE_POST_COUNT_LIMIT);
229
351
  return collections.filter((c) => c.archives && c.basePath).flatMap((c) => {
230
352
  const cats = collectCollectionCategories(posts, collections, c.key);
231
353
  if (!cats.length) return [];
232
354
  const base = `${siteUrl}${trimSlash(c.basePath)}`;
233
355
  return [
234
356
  { url: `${base}/categories` },
235
- ...cats.map((category) => ({
357
+ ...cats.filter(inSitemap).map((category) => ({
236
358
  url: `${base}/categories/${category.slug}`
237
359
  }))
238
360
  ];
239
361
  });
240
362
  }
241
- const categories = collectCategories(posts);
363
+ const categories = await safeBlogArchiveCategories(config);
242
364
  if (!categories.length) return [];
243
365
  const blogBase = config.blogBasePath ?? "/blog";
244
366
  return [
245
367
  { url: `${siteUrl}${blogBase}/categories` },
246
- ...categories.map((category) => ({
368
+ ...categories.filter(inSitemap).map((category) => ({
247
369
  url: `${siteUrl}${blogBase}/categories/${category.slug}`
248
370
  }))
249
371
  ];
250
372
  }
373
+ async function authorEntries(config, siteUrl, settings, authorBasePath) {
374
+ if (!settings.authors) return [];
375
+ const authors = await safeFetchAuthors(config);
376
+ const base = trimSlash(authorBasePath ?? "/blog/author");
377
+ const promotion = resolveAuthorPromotion(settings.authors);
378
+ return authors.filter(
379
+ (a) => decideArchiveIndex({
380
+ kind: "author",
381
+ promotion,
382
+ postCount: a.postCount
383
+ }).inSitemap
384
+ ).map((a) => ({
385
+ url: `${siteUrl}${base}/${encodeURIComponent(a.slug)}`,
386
+ lastModified: a.lastUpdatedAt ?? void 0
387
+ }));
388
+ }
389
+
390
+ // src/routes-feed-sitemap.ts
391
+ function createFeedRoute(config) {
392
+ return async function GET() {
393
+ const siteUrl = trimSlash(config.siteUrl);
394
+ const collections = await resolveCollections(config.collections);
395
+ const allPosts = await safeFetchPosts(config, 50);
396
+ const inCollectionMode = isInCollectionMode2(collections);
397
+ const blogBase = config.blogBasePath ?? "/blog";
398
+ const posts = inCollectionMode ? allPosts.filter((p) => {
399
+ const c = resolvePostCollection3(p, collections);
400
+ return c ? c.feed === true && c.detail !== false && c.basePath !== "" : false;
401
+ }) : allPosts;
402
+ const xml = generateRssXml({
403
+ title: config.title,
404
+ link: inCollectionMode ? siteUrl : `${siteUrl}${blogBase}`,
405
+ feedUrl: `${siteUrl}/feed.xml`,
406
+ description: config.description,
407
+ language: config.language,
408
+ items: rssItemsFromPosts(posts, {
409
+ postUrl: (p) => inCollectionMode ? resolvePostUrl3(siteUrl, p, collections) ?? `${siteUrl}${blogBase}/${p.slug}` : `${siteUrl}${blogBase}/${p.slug}`
410
+ })
411
+ });
412
+ return new Response(xml, {
413
+ headers: {
414
+ "content-type": "application/rss+xml; charset=utf-8",
415
+ "cache-control": "public, max-age=1800, s-maxage=1800"
416
+ }
417
+ });
418
+ };
419
+ }
251
420
  function createSitemap(config, staticRoutes = []) {
252
421
  return async function sitemap() {
253
422
  const siteUrl = trimSlash(config.siteUrl);
254
423
  const collections = await resolveCollections(config.collections);
255
- const posts = await safeFetchPosts(config, 200);
256
- const settings = await resolveSitemapSettings(config, {
424
+ const posts = await safeFetchPosts(config, ARCHIVE_POST_COUNT_LIMIT);
425
+ const { settings } = await resolveSitemapSettings(config, {
257
426
  skipRemoteSettings: true,
258
427
  blogImages: false
259
428
  });
260
429
  return [
261
430
  ...staticEntries(staticRoutes),
262
431
  ...postEntries(config, siteUrl, posts, collections, settings),
263
- ...categoryEntries(config, siteUrl, posts, collections)
432
+ // settings.promotedCategorySlugs 는 여기서 항상 undefined(위 skipRemoteSettings)
433
+ // → 색인 정책 미적용 = 레거시 동등. 정책이 필요하면 createSitemapIndex 로.
434
+ ...await categoryEntries(config, siteUrl, collections, void 0)
264
435
  ];
265
436
  };
266
437
  }
267
438
  function createSitemapIndex(config, staticRoutes = [], opts = {}) {
268
439
  async function generateSitemaps() {
269
- const settings = await resolveSitemapSettings(config, opts);
440
+ const { settings, settingsFetchFailed } = await resolveSitemapSettings(
441
+ config,
442
+ opts
443
+ );
270
444
  const ids = ["static", "blog", "categories"];
271
- if (settings.authors) ids.push("authors");
445
+ if (settings.authors || settingsFetchFailed) ids.push("authors");
272
446
  return ids.map((id) => ({ id }));
273
447
  }
274
448
  async function sitemap(props) {
275
449
  const id = await props.id;
276
450
  const siteUrl = trimSlash(config.siteUrl);
277
- const settings = await resolveSitemapSettings(config, opts);
451
+ const { settings } = await resolveSitemapSettings(config, opts);
278
452
  const activeLocales = await localesWithPublishedContent(config, settings.locales);
279
453
  const defaultLocale = activeLocales[0];
280
454
  if (id === "static") return staticEntries(staticRoutes);
281
455
  if (id === "authors") {
282
- if (!settings.authors) return [];
283
- const authors = await safeFetchAuthors(config);
284
- const base = trimSlash(opts.authorBasePath ?? "/blog/author");
285
- return authors.map((a) => ({
286
- url: `${siteUrl}${base}/${encodeURIComponent(a.slug)}`,
287
- lastModified: a.lastUpdatedAt ?? void 0
288
- }));
456
+ return authorEntries(config, siteUrl, settings, opts.authorBasePath);
289
457
  }
290
458
  const collections = await resolveCollections(config.collections);
291
- const posts = await safeFetchPosts(config, 200);
459
+ if (id === "categories") {
460
+ return categoryEntries(
461
+ config,
462
+ siteUrl,
463
+ collections,
464
+ settings.promotedCategorySlugs
465
+ );
466
+ }
292
467
  if (id === "blog") {
468
+ const posts = await safeFetchPosts(config, ARCHIVE_POST_COUNT_LIMIT);
293
469
  return postEntries(config, siteUrl, posts, collections, settings, defaultLocale);
294
470
  }
295
- if (id === "categories") {
296
- return categoryEntries(config, siteUrl, posts, collections);
297
- }
298
471
  return [];
299
472
  }
300
473
  return { generateSitemaps, sitemap };
301
474
  }
302
475
 
303
476
  // src/routes-llms.ts
304
- import { isInCollectionMode as isInCollectionMode2, resolvePostUrl as resolvePostUrl3 } from "@roottale/cms-core";
477
+ import { isInCollectionMode as isInCollectionMode3, resolvePostUrl as resolvePostUrl4 } from "@roottale/cms-core";
305
478
  function mdLinkSafe(text) {
306
479
  return text.replace(/[[\]()]/g, "").trim();
307
480
  }
@@ -326,10 +499,10 @@ function createLlmsTxtRoute(config) {
326
499
  }
327
500
  }
328
501
  if (posts.length > 0) {
329
- const inCollectionMode = isInCollectionMode2(collections);
502
+ const inCollectionMode = isInCollectionMode3(collections);
330
503
  lines.push("", inCollectionMode ? "## \uCF58\uD150\uCE20" : "## \uBE14\uB85C\uADF8");
331
504
  for (const p of posts) {
332
- const url = inCollectionMode ? resolvePostUrl3(siteUrl, p, collections) : `${siteUrl}${blogBase}/${p.slug}`;
505
+ const url = inCollectionMode ? resolvePostUrl4(siteUrl, p, collections) : `${siteUrl}${blogBase}/${p.slug}`;
333
506
  if (!url) continue;
334
507
  const excerpt = p.excerpt ? `: ${oneLine(p.excerpt)}` : "";
335
508
  lines.push(`- [${mdLinkSafe(p.title)}](${url})${excerpt}`);
@@ -350,7 +523,7 @@ function createLlmsTxtRoute(config) {
350
523
  import {
351
524
  verifyRootTaleWebhook
352
525
  } from "@roottale/cms-client/webhook";
353
- import { isInCollectionMode as isInCollectionMode3 } from "@roottale/cms-core";
526
+ import { isInCollectionMode as isInCollectionMode4 } from "@roottale/cms-core";
354
527
  function pathsFromBody(rawBody) {
355
528
  try {
356
529
  const body = JSON.parse(rawBody);
@@ -406,7 +579,7 @@ function createRevalidateRoute(config) {
406
579
  if (failed) {
407
580
  alsoRevalidate = INVARIANTS;
408
581
  warning = "collections_resolve_failed";
409
- } else if (isInCollectionMode3(collections)) {
582
+ } else if (isInCollectionMode4(collections)) {
410
583
  alsoRevalidate = [
411
584
  ...INVARIANTS,
412
585
  // 주소 없는 유형(basePath === "")은 라우트가 없으므로 revalidate 대상에서
@@ -657,7 +830,7 @@ function createFleetInfoRoute(config = {}) {
657
830
  site: config.site ?? env.VERCEL_PROJECT_PRODUCTION_URL ?? null,
658
831
  framework: "next",
659
832
  cms_client: CMS_CLIENT_VERSION,
660
- cms_renderer: true ? "0.48.0" : "dev",
833
+ cms_renderer: true ? "0.51.0" : "dev",
661
834
  build_sha: env.VERCEL_GIT_COMMIT_SHA?.slice(0, 7) ?? null
662
835
  },
663
836
  { headers: { "cache-control": "public, max-age=300" } }
@@ -665,8 +838,10 @@ function createFleetInfoRoute(config = {}) {
665
838
  };
666
839
  }
667
840
  export {
841
+ ARCHIVE_POST_COUNT_LIMIT,
668
842
  OG_IMAGE_CONTENT_TYPE,
669
843
  OG_IMAGE_SIZE,
844
+ buildArchiveMetadata,
670
845
  buildHreflangLanguages,
671
846
  buildPostMetadata,
672
847
  createFeedRoute,
@@ -677,13 +852,19 @@ export {
677
852
  createRevalidateRoute,
678
853
  createSitemap,
679
854
  createSitemapIndex,
855
+ decideArchiveIndex,
856
+ fetchBlogArchiveCategories,
857
+ fetchBlogArchivePosts,
680
858
  matchRedirect,
681
859
  normalizeRedirectPath,
682
860
  postRedirectPath,
861
+ resolveAuthorPromotion,
862
+ resolveCategoryPromotion,
683
863
  resolveCollections,
684
864
  resolveCollectionsResult,
685
865
  resolvePostCollection,
686
866
  resolvePostUrl,
867
+ resolveRobotsMeta,
687
868
  safeFetchPosts,
688
869
  trimSlash
689
870
  };