@consilioweb/payload-seo-analyzer 1.10.0 → 1.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/dist/index.cjs CHANGED
@@ -3443,6 +3443,130 @@ function createAiAltTextHandler(uploadsCollection, seoConfig) {
3443
3443
  };
3444
3444
  }
3445
3445
 
3446
+ // src/endpoints/aiContentBrief.ts
3447
+ var DEFAULT_MODEL3 = "claude-opus-4-8";
3448
+ var trimList = (arr, max, itemMax = 160) => Array.isArray(arr) ? arr.filter((x) => typeof x === "string").map((x) => x.trim()).filter(Boolean).slice(0, max).map((x) => x.length > itemMax ? `${x.slice(0, itemMax - 1)}\u2026` : x) : [];
3449
+ function parseBrief(raw) {
3450
+ let s = raw.trim();
3451
+ if (s.startsWith("```")) s = s.replace(/^```(?:json)?\s*/i, "").replace(/\s*```$/i, "").trim();
3452
+ if (!s.startsWith("{")) {
3453
+ const start = s.indexOf("{");
3454
+ const end = s.lastIndexOf("}");
3455
+ if (start === -1 || end === -1 || end <= start) return null;
3456
+ s = s.slice(start, end + 1);
3457
+ }
3458
+ try {
3459
+ const p = JSON.parse(s);
3460
+ return sanitizeBrief({
3461
+ outline: Array.isArray(p.outline) ? p.outline.map((o) => {
3462
+ const r = o || {};
3463
+ return { level: r.level === "h3" ? "h3" : "h2", text: typeof r.text === "string" ? r.text : "" };
3464
+ }) : [],
3465
+ entities: trimList(p.entities, 30),
3466
+ questions: trimList(p.questions, 15),
3467
+ internalLinkIdeas: trimList(p.internalLinkIdeas, 10),
3468
+ recommendedWordCount: typeof p.recommendedWordCount === "number" ? p.recommendedWordCount : 0,
3469
+ notes: trimList(p.notes, 6)
3470
+ });
3471
+ } catch {
3472
+ return null;
3473
+ }
3474
+ }
3475
+ function sanitizeBrief(b) {
3476
+ return {
3477
+ outline: b.outline.filter((o) => o.text && o.text.trim()).slice(0, 25).map((o) => ({ level: o.level === "h3" ? "h3" : "h2", text: o.text.trim().slice(0, 160) })),
3478
+ entities: trimList(b.entities, 30),
3479
+ questions: trimList(b.questions, 15),
3480
+ internalLinkIdeas: trimList(b.internalLinkIdeas, 10),
3481
+ recommendedWordCount: Math.min(1e4, Math.max(0, Math.round(b.recommendedWordCount || 0))),
3482
+ notes: trimList(b.notes, 6)
3483
+ };
3484
+ }
3485
+ async function callClaudeBrief(apiKey, model, language, params) {
3486
+ const systemPrompt = `You are an SEO content strategist applying June 2026 best practices.
3487
+ Produce a concise WRITING BRIEF for the target keyword so a writer can create a page that ranks AND is citable by AI engines.
3488
+ Rules:
3489
+ - Base the brief on genuine search intent for the keyword; cover entities and questions a complete page must address.
3490
+ - Be specific and non-generic; no filler. Write in ${language === "en" ? "English" : "French"}.
3491
+ - Do not invent facts, brands, prices or statistics.
3492
+ Return ONLY a JSON object (no markdown, no prose) with EXACTLY this shape:
3493
+ {"outline":[{"level":"h2"|"h3","text":string}],"entities":[string],"questions":[string],"internalLinkIdeas":[string],"recommendedWordCount":number,"notes":[string]}
3494
+ - outline: 5-12 headings (logical H2/H3 structure).
3495
+ - entities: 8-20 key terms/concepts to mention.
3496
+ - questions: 4-10 questions the page should answer (People-Also-Ask style).
3497
+ - internalLinkIdeas: 3-8 topics worth linking to internally.
3498
+ - notes: up to 4 short strategic tips.`;
3499
+ const userPrompt = `Target keyword: ${params.keyword}
3500
+ ${params.pageTitle ? `Existing page title: ${params.pageTitle}` : ""}
3501
+ ${params.existingContent ? `Existing content (first 2000 chars, complement it \u2014 don't repeat):
3502
+ ${params.existingContent.substring(0, 2e3)}` : ""}
3503
+
3504
+ Return the JSON brief now:`;
3505
+ const response = await fetch("https://api.anthropic.com/v1/messages", {
3506
+ method: "POST",
3507
+ headers: { "Content-Type": "application/json", "x-api-key": apiKey, "anthropic-version": "2023-06-01" },
3508
+ body: JSON.stringify({
3509
+ model,
3510
+ max_tokens: 1500,
3511
+ system: systemPrompt,
3512
+ messages: [{ role: "user", content: userPrompt }]
3513
+ })
3514
+ });
3515
+ if (!response.ok) {
3516
+ const body = await response.text();
3517
+ throw new Error(`Claude API error ${response.status}: ${body}`);
3518
+ }
3519
+ const data = await response.json();
3520
+ if (data.stop_reason === "refusal") return null;
3521
+ const text = (data.content?.find((b) => b.type === "text")?.text || "").trim();
3522
+ if (!text) return null;
3523
+ return parseBrief(text);
3524
+ }
3525
+ function createAiContentBriefHandler(targetCollections, seoConfig) {
3526
+ return async (req) => {
3527
+ try {
3528
+ if (!req.user) return Response.json({ error: "Unauthorized" }, { status: 401 });
3529
+ const body = await parseJsonBody(req);
3530
+ const keyword = typeof body.keyword === "string" ? body.keyword.trim() : "";
3531
+ if (!keyword) return Response.json({ error: "Missing required field: keyword" }, { status: 400 });
3532
+ const apiKey = process.env.ANTHROPIC_API_KEY;
3533
+ if (!apiKey) {
3534
+ return Response.json(
3535
+ { error: "AI not configured. Set ANTHROPIC_API_KEY to generate a content brief.", code: "no_api_key" },
3536
+ { status: 400 }
3537
+ );
3538
+ }
3539
+ let pageTitle;
3540
+ let existingContent;
3541
+ const collection = typeof body.collection === "string" ? body.collection : void 0;
3542
+ const id = body.id != null ? String(body.id) : void 0;
3543
+ if (collection && id && (!targetCollections || targetCollections.includes(collection))) {
3544
+ try {
3545
+ const doc = await req.payload.findByID({ collection, id, depth: 1, overrideAccess: true });
3546
+ pageTitle = doc.title || void 0;
3547
+ existingContent = extractDocContent(doc).text || void 0;
3548
+ } catch {
3549
+ }
3550
+ }
3551
+ const model = process.env.SEO_AI_MODEL || DEFAULT_MODEL3;
3552
+ const language = seoConfig?.locale === "en" ? "en" : "fr";
3553
+ let brief;
3554
+ try {
3555
+ brief = await callClaudeBrief(apiKey, model, language, { keyword, pageTitle, existingContent });
3556
+ } catch (e) {
3557
+ req.payload.logger.error(`[seo] ai-content-brief Claude error: ${e instanceof Error ? e.message : "unknown"}`);
3558
+ return Response.json({ error: "Content brief generation failed." }, { status: 502 });
3559
+ }
3560
+ if (!brief) return Response.json({ error: "The model did not return a brief (possibly declined)." }, { status: 502 });
3561
+ return Response.json({ keyword, brief, model });
3562
+ } catch (error) {
3563
+ const message = error instanceof Error ? error.message : "Internal server error";
3564
+ req.payload.logger.error(`[seo] ai-content-brief error: ${message}`);
3565
+ return Response.json({ error: message }, { status: 500 });
3566
+ }
3567
+ };
3568
+ }
3569
+
3446
3570
  // src/endpoints/cannibalization.ts
3447
3571
  function canonicalIntent(keyword) {
3448
3572
  return keyword.toLowerCase().normalize("NFD").replace(/\p{Diacritic}/gu, "").replace(/[^\p{L}\p{N}\s]/gu, " ").split(/\s+/).filter(Boolean).sort().join(" ");
@@ -5240,24 +5364,46 @@ function buildArticleSchema(doc, siteUrl) {
5240
5364
  }
5241
5365
  return schema;
5242
5366
  }
5243
- function buildLocalBusinessSchema(doc, siteUrl) {
5367
+ function buildLocationNode(loc, doc, siteUrl) {
5244
5368
  const meta = doc.meta || {};
5245
- const schema = {
5246
- "@context": "https://schema.org",
5247
- "@type": "LocalBusiness",
5248
- name: doc.title || meta.title || "",
5249
- description: meta.description || "",
5250
- url: `${siteUrl}/${doc.slug || ""}`
5369
+ const node = {
5370
+ "@type": typeof loc.type === "string" && loc.type || "LocalBusiness",
5371
+ name: loc.name || doc.title || meta.title || "",
5372
+ description: loc.description || meta.description || "",
5373
+ url: loc.url || `${siteUrl}/${doc.slug || ""}`
5251
5374
  };
5252
- if (doc.telephone) schema.telephone = doc.telephone;
5253
- if (doc.email) schema.email = doc.email;
5254
- if (doc.address && typeof doc.address === "object") {
5255
- schema.address = {
5256
- "@type": "PostalAddress",
5257
- ...doc.address
5375
+ if (loc.telephone) node.telephone = loc.telephone;
5376
+ if (loc.email) node.email = loc.email;
5377
+ if (loc.priceRange) node.priceRange = loc.priceRange;
5378
+ const address = loc.address;
5379
+ if (address && typeof address === "object") {
5380
+ node.address = { "@type": "PostalAddress", ...address };
5381
+ } else if (typeof address === "string" && address) {
5382
+ node.address = address;
5383
+ }
5384
+ const geo = loc.geo || {};
5385
+ const lat = geo.latitude ?? loc.latitude ?? loc.lat;
5386
+ const lng = geo.longitude ?? loc.longitude ?? loc.lng;
5387
+ if (lat != null && lng != null) {
5388
+ node.geo = { "@type": "GeoCoordinates", latitude: lat, longitude: lng };
5389
+ }
5390
+ if (Array.isArray(loc.openingHours) && loc.openingHours.length > 0) {
5391
+ node.openingHours = loc.openingHours;
5392
+ } else if (typeof loc.openingHours === "string" && loc.openingHours) {
5393
+ node.openingHours = loc.openingHours;
5394
+ }
5395
+ return node;
5396
+ }
5397
+ function buildLocalBusinessSchema(doc, siteUrl) {
5398
+ const locations = Array.isArray(doc.locations) ? doc.locations.filter((l) => !!l && typeof l === "object") : [];
5399
+ if (locations.length > 1) {
5400
+ return {
5401
+ "@context": "https://schema.org",
5402
+ "@graph": locations.map((loc) => buildLocationNode(loc, doc, siteUrl))
5258
5403
  };
5259
5404
  }
5260
- return schema;
5405
+ const base = locations.length === 1 ? locations[0] : doc;
5406
+ return { "@context": "https://schema.org", ...buildLocationNode(base, doc, siteUrl) };
5261
5407
  }
5262
5408
  function buildBreadcrumbSchema(doc, siteUrl) {
5263
5409
  const slug = doc.slug || "";
@@ -6008,6 +6154,191 @@ function createSitemapHandler(targetCollections) {
6008
6154
  };
6009
6155
  }
6010
6156
 
6157
+ // src/endpoints/sitemapExtensions.ts
6158
+ function escapeXml2(str) {
6159
+ return str.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&apos;");
6160
+ }
6161
+ function resolveSiteUrl3(seoConfig) {
6162
+ return (seoConfig?.siteUrl || process.env.NEXT_PUBLIC_SERVER_URL || process.env.PAYLOAD_PUBLIC_SERVER_URL || "").replace(/\/$/, "");
6163
+ }
6164
+ function docPath(slug) {
6165
+ return slug === "home" || slug === "" ? "" : `/${slug}`;
6166
+ }
6167
+ function xmlResponse(xml, status = 200) {
6168
+ return new Response(xml, {
6169
+ status,
6170
+ headers: { "Content-Type": "application/xml", "Cache-Control": "public, max-age=3600, s-maxage=3600" }
6171
+ });
6172
+ }
6173
+ function mediaUrl(media, siteUrl) {
6174
+ if (typeof media.url === "string" && media.url) {
6175
+ return media.url.startsWith("http") ? media.url : `${siteUrl}${media.url}`;
6176
+ }
6177
+ if (typeof media.filename === "string" && media.filename) {
6178
+ return `${siteUrl}/media/${media.filename}`;
6179
+ }
6180
+ return void 0;
6181
+ }
6182
+ function collectMediaUrls(node, mimePrefix, siteUrl, out, depth = 0) {
6183
+ if (!node || typeof node !== "object" || depth > 8) return;
6184
+ if (Array.isArray(node)) {
6185
+ for (const item of node) collectMediaUrls(item, mimePrefix, siteUrl, out, depth + 1);
6186
+ return;
6187
+ }
6188
+ const obj = node;
6189
+ const mime = typeof obj.mimeType === "string" ? obj.mimeType : "";
6190
+ if (mime.startsWith(mimePrefix)) {
6191
+ const url = mediaUrl(obj, siteUrl);
6192
+ if (url) out.add(url);
6193
+ }
6194
+ for (const key of Object.keys(obj)) {
6195
+ if (key === "sizes" || key === "_status") continue;
6196
+ collectMediaUrls(obj[key], mimePrefix, siteUrl, out, depth + 1);
6197
+ }
6198
+ }
6199
+ async function eachPublishedDoc(payload, collections, depth, onDoc) {
6200
+ const BATCH = Math.min(100, Math.max(1, parseInt(process.env.SEO_SITEMAP_BATCH_SIZE || "50", 10) || 50));
6201
+ const MAX = Math.max(1, parseInt(process.env.SEO_SITEMAP_MAX_DOCS || "5000", 10) || 5e3);
6202
+ let count = 0;
6203
+ for (const collection of collections) {
6204
+ try {
6205
+ let page = 1;
6206
+ let hasMore = true;
6207
+ while (hasMore) {
6208
+ const res = await payload.find({ collection, limit: BATCH, page, depth, overrideAccess: true });
6209
+ for (const doc of res.docs) {
6210
+ if (doc._status === "draft") continue;
6211
+ if (count >= MAX) return;
6212
+ onDoc(doc, collection);
6213
+ count++;
6214
+ }
6215
+ hasMore = res.hasNextPage;
6216
+ page++;
6217
+ await new Promise((resolve) => setImmediate(resolve));
6218
+ }
6219
+ } catch {
6220
+ }
6221
+ }
6222
+ }
6223
+ function createNewsSitemapHandler(targetCollections, seoConfig) {
6224
+ return async (req) => {
6225
+ try {
6226
+ const siteUrl = resolveSiteUrl3(seoConfig);
6227
+ const language = seoConfig?.locale === "en" ? "en" : "fr";
6228
+ let publication = seoConfig?.siteName || "";
6229
+ if (!publication && siteUrl) {
6230
+ try {
6231
+ publication = new URL(siteUrl).hostname;
6232
+ } catch {
6233
+ }
6234
+ }
6235
+ const cutoff = Date.now() - 48 * 36e5;
6236
+ const entries = [];
6237
+ await eachPublishedDoc(req.payload, targetCollections, 0, (doc) => {
6238
+ const dateStr = typeof doc.publishedAt === "string" && doc.publishedAt || typeof doc.date === "string" && doc.date || typeof doc.createdAt === "string" && doc.createdAt || "";
6239
+ if (!dateStr) return;
6240
+ const t = new Date(dateStr).getTime();
6241
+ if (isNaN(t) || t < cutoff) return;
6242
+ const title = doc.title || doc.meta?.title || "";
6243
+ if (!title) return;
6244
+ const loc = `${siteUrl}${docPath(doc.slug || "")}`;
6245
+ entries.push(
6246
+ ` <url>
6247
+ <loc>${escapeXml2(loc)}</loc>
6248
+ <news:news>
6249
+ <news:publication>
6250
+ <news:name>${escapeXml2(publication)}</news:name>
6251
+ <news:language>${language}</news:language>
6252
+ </news:publication>
6253
+ <news:publication_date>${new Date(dateStr).toISOString()}</news:publication_date>
6254
+ <news:title>${escapeXml2(title)}</news:title>
6255
+ </news:news>
6256
+ </url>`
6257
+ );
6258
+ });
6259
+ const xml = `<?xml version="1.0" encoding="UTF-8"?>
6260
+ <urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9" xmlns:news="http://www.google.com/schemas/sitemap-news/0.9">
6261
+ ${entries.join("\n")}
6262
+ </urlset>`;
6263
+ return xmlResponse(xml);
6264
+ } catch (error) {
6265
+ req.payload.logger.error(`[seo] sitemap-news error: ${error instanceof Error ? error.message : "unknown"}`);
6266
+ return xmlResponse('<?xml version="1.0" encoding="UTF-8"?>\n<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"></urlset>', 500);
6267
+ }
6268
+ };
6269
+ }
6270
+ function createImageSitemapHandler(targetCollections, seoConfig) {
6271
+ return async (req) => {
6272
+ try {
6273
+ const siteUrl = resolveSiteUrl3(seoConfig);
6274
+ const entries = [];
6275
+ await eachPublishedDoc(req.payload, targetCollections, 1, (doc) => {
6276
+ const urls = /* @__PURE__ */ new Set();
6277
+ collectMediaUrls(doc, "image/", siteUrl, urls);
6278
+ if (urls.size === 0) return;
6279
+ const loc = `${siteUrl}${docPath(doc.slug || "")}`;
6280
+ const imgs = Array.from(urls).slice(0, 1e3).map((u) => ` <image:image><image:loc>${escapeXml2(u)}</image:loc></image:image>`).join("\n");
6281
+ entries.push(` <url>
6282
+ <loc>${escapeXml2(loc)}</loc>
6283
+ ${imgs}
6284
+ </url>`);
6285
+ });
6286
+ const xml = `<?xml version="1.0" encoding="UTF-8"?>
6287
+ <urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9" xmlns:image="http://www.google.com/schemas/sitemap-image/1.1">
6288
+ ${entries.join("\n")}
6289
+ </urlset>`;
6290
+ return xmlResponse(xml);
6291
+ } catch (error) {
6292
+ req.payload.logger.error(`[seo] sitemap-images error: ${error instanceof Error ? error.message : "unknown"}`);
6293
+ return xmlResponse('<?xml version="1.0" encoding="UTF-8"?>\n<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"></urlset>', 500);
6294
+ }
6295
+ };
6296
+ }
6297
+ function createVideoSitemapHandler(targetCollections, seoConfig) {
6298
+ return async (req) => {
6299
+ try {
6300
+ const siteUrl = resolveSiteUrl3(seoConfig);
6301
+ const entries = [];
6302
+ await eachPublishedDoc(req.payload, targetCollections, 1, (doc) => {
6303
+ const meta = doc.meta || {};
6304
+ const videoUrls = /* @__PURE__ */ new Set();
6305
+ collectMediaUrls(doc, "video/", siteUrl, videoUrls);
6306
+ for (const k of ["videoUrl", "contentUrl", "playerUrl"]) {
6307
+ if (typeof doc[k] === "string" && doc[k]) videoUrls.add(doc[k]);
6308
+ }
6309
+ if (videoUrls.size === 0) return;
6310
+ const title = doc.title || meta.title || "";
6311
+ const description = meta.description || title;
6312
+ const thumbs = /* @__PURE__ */ new Set();
6313
+ collectMediaUrls(meta.image, "image/", siteUrl, thumbs);
6314
+ if (thumbs.size === 0) collectMediaUrls(doc, "image/", siteUrl, thumbs);
6315
+ const thumbnail = Array.from(thumbs)[0] || "";
6316
+ const loc = `${siteUrl}${docPath(doc.slug || "")}`;
6317
+ const videos = Array.from(videoUrls).slice(0, 100).map(
6318
+ (u) => ` <video:video>
6319
+ ${thumbnail ? ` <video:thumbnail_loc>${escapeXml2(thumbnail)}</video:thumbnail_loc>
6320
+ ` : ""} <video:title>${escapeXml2(title || "Video")}</video:title>
6321
+ <video:description>${escapeXml2(description || title || "Video")}</video:description>
6322
+ <video:content_loc>${escapeXml2(u)}</video:content_loc>
6323
+ </video:video>`
6324
+ ).join("\n");
6325
+ entries.push(` <url>
6326
+ <loc>${escapeXml2(loc)}</loc>
6327
+ ${videos}
6328
+ </url>`);
6329
+ });
6330
+ const xml = `<?xml version="1.0" encoding="UTF-8"?>
6331
+ <urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9" xmlns:video="http://www.google.com/schemas/sitemap-video/1.1">
6332
+ ${entries.join("\n")}
6333
+ </urlset>`;
6334
+ return xmlResponse(xml);
6335
+ } catch (error) {
6336
+ req.payload.logger.error(`[seo] sitemap-video error: ${error instanceof Error ? error.message : "unknown"}`);
6337
+ return xmlResponse('<?xml version="1.0" encoding="UTF-8"?>\n<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"></urlset>', 500);
6338
+ }
6339
+ };
6340
+ }
6341
+
6011
6342
  // src/collections/SeoScoreHistory.ts
6012
6343
  function createSeoScoreHistoryCollection() {
6013
6344
  return {
@@ -8496,6 +8827,9 @@ var fr = {
8496
8827
  pagesAnalyzed: "pages analys\xE9es",
8497
8828
  markCornerstone: "Marquer pilier",
8498
8829
  unmarkCornerstone: "D\xE9marquer pilier",
8830
+ bulkOptimizeMeta: "Optimiser m\xE9ta (IA)",
8831
+ bulkOptimizing: "Optimisation\u2026",
8832
+ bulkConfirm: "Confirmer ?",
8499
8833
  searchPlaceholder: "Rechercher (titre, slug, keyword)...",
8500
8834
  allCollections: "Toutes les collections",
8501
8835
  allScores: "Tous les scores",
@@ -9088,6 +9422,9 @@ var en = {
9088
9422
  pagesAnalyzed: "pages analyzed",
9089
9423
  markCornerstone: "Mark as cornerstone",
9090
9424
  unmarkCornerstone: "Unmark cornerstone",
9425
+ bulkOptimizeMeta: "Optimize meta (AI)",
9426
+ bulkOptimizing: "Optimizing\u2026",
9427
+ bulkConfirm: "Confirm?",
9091
9428
  searchPlaceholder: "Search (title, slug, keyword)...",
9092
9429
  allCollections: "All collections",
9093
9430
  allScores: "All scores",
@@ -9939,7 +10276,8 @@ var seoAnalyzerPlugin = (pluginConfig = {}) => (incomingConfig) => {
9939
10276
  { path: `${basePath}/ai-rewrite`, method: "post", handler: createAiRewriteHandler(targetCollections) },
9940
10277
  { path: `${basePath}/ai-optimize`, method: "post", handler: createAiOptimizeHandler(targetCollections, seoConfig) },
9941
10278
  { path: `${basePath}/alt-text-audit`, method: "get", handler: createAltTextAuditHandler(uploadsCollection) },
9942
- { path: `${basePath}/ai-alt-text`, method: "post", handler: withRateLimit(createAiAltTextHandler(uploadsCollection, seoConfig)) }
10279
+ { path: `${basePath}/ai-alt-text`, method: "post", handler: withRateLimit(createAiAltTextHandler(uploadsCollection, seoConfig)) },
10280
+ { path: `${basePath}/ai-content-brief`, method: "post", handler: withRateLimit(createAiContentBriefHandler(targetCollections, seoConfig)) }
9943
10281
  );
9944
10282
  }
9945
10283
  if (features.cannibalization) {
@@ -10031,6 +10369,21 @@ var seoAnalyzerPlugin = (pluginConfig = {}) => (incomingConfig) => {
10031
10369
  path: `${basePath}/sitemap.xml`,
10032
10370
  method: "get",
10033
10371
  handler: createSitemapHandler(targetCollections)
10372
+ },
10373
+ {
10374
+ path: `${basePath}/sitemap-news.xml`,
10375
+ method: "get",
10376
+ handler: createNewsSitemapHandler(targetCollections, seoConfig)
10377
+ },
10378
+ {
10379
+ path: `${basePath}/sitemap-images.xml`,
10380
+ method: "get",
10381
+ handler: createImageSitemapHandler(targetCollections, seoConfig)
10382
+ },
10383
+ {
10384
+ path: `${basePath}/sitemap-video.xml`,
10385
+ method: "get",
10386
+ handler: createVideoSitemapHandler(targetCollections, seoConfig)
10034
10387
  }
10035
10388
  );
10036
10389
  config.endpoints = [
@@ -10135,7 +10488,7 @@ var seoAnalyzerPlugin = (pluginConfig = {}) => (incomingConfig) => {
10135
10488
  };
10136
10489
 
10137
10490
  // src/helpers/buildMetadata.ts
10138
- function resolveSiteUrl3(explicit) {
10491
+ function resolveSiteUrl4(explicit) {
10139
10492
  return (explicit || process.env.NEXT_PUBLIC_SERVER_URL || process.env.PAYLOAD_PUBLIC_SERVER_URL || "").replace(/\/$/, "");
10140
10493
  }
10141
10494
  function parseRobots(doc, meta) {
@@ -10169,7 +10522,7 @@ function absoluteUrl(value, siteUrl) {
10169
10522
  return `${siteUrl}${value.startsWith("/") ? "" : "/"}${value}`;
10170
10523
  }
10171
10524
  function buildSeoMetadata(doc, options = {}) {
10172
- const siteUrl = resolveSiteUrl3(options.siteUrl);
10525
+ const siteUrl = resolveSiteUrl4(options.siteUrl);
10173
10526
  const meta = doc.meta || {};
10174
10527
  const rawTitle = meta.title || doc.title || "";
10175
10528
  const title = options.titleTemplate && rawTitle ? options.titleTemplate.replace("%s", rawTitle) : rawTitle;
package/dist/index.d.cts CHANGED
@@ -467,6 +467,9 @@ interface DashboardTranslations {
467
467
  pagesAnalyzed: string;
468
468
  markCornerstone: string;
469
469
  unmarkCornerstone: string;
470
+ bulkOptimizeMeta: string;
471
+ bulkOptimizing: string;
472
+ bulkConfirm: string;
470
473
  searchPlaceholder: string;
471
474
  allCollections: string;
472
475
  allScores: string;
package/dist/index.d.ts CHANGED
@@ -467,6 +467,9 @@ interface DashboardTranslations {
467
467
  pagesAnalyzed: string;
468
468
  markCornerstone: string;
469
469
  unmarkCornerstone: string;
470
+ bulkOptimizeMeta: string;
471
+ bulkOptimizing: string;
472
+ bulkConfirm: string;
470
473
  searchPlaceholder: string;
471
474
  allCollections: string;
472
475
  allScores: string;