next-advanced-sitemap 1.0.7 → 1.0.9

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/README.md CHANGED
@@ -12,6 +12,7 @@ While Next.js provides a built-in `MetadataRoute.Sitemap` utility, it currently
12
12
  - **Google Video Support**: Improve search visibility for video content with thumbnail and description metadata.
13
13
  - **Google News Support**: Comply with Google News requirements including publication names and dates.
14
14
  - **Internationalization**: Seamless integration of `xhtml:link` tags for Hreflang and multi-regional SEO.
15
+ - **Priority Auto-Sorting (v1.0.8)**: Optional deterministic descending sort (`1.0` to `0.0`) based on entry weights to present your most strategic pages to crawlers first.
15
16
  - **Auto-Trimming Sanitization (v1.0.7)**: Automatic `.trim()` execution on all URL fields to silently correct leading/trailing whitespace errors from CMS or databases.
16
17
  - **Native Date Polymorphism (v1.0.6)**: Full support for native JavaScript `Date` objects inside Google News and Video extensions—no manual conversion required.
17
18
  - **Strict SEO Enum Typing (v1.0.5)**: Compile-time validation and IDE autocompletion for `changefreq` and `priority` values to prevent typos.
@@ -19,6 +20,7 @@ While Next.js provides a built-in `MetadataRoute.Sitemap` utility, it currently
19
20
  - **Auto-lastmod (v1.0.3)**: Optional automatic injection of the current system date for entries missing a `lastmod` value.
20
21
  - **Advanced XML Escaping (v1.0.2)**: Enhanced processor to handle complex special characters (`&`, `"`, `'`, `<`, `>`) in SEO metadata, ensuring XML integrity.
21
22
  - **Developer Experience**: Fully typed with TypeScript, zero external dependencies, and optimized for Next.js Route Handlers.
23
+ - **Custom TTL Cache-Control (v1.0.9)**: Direct control over sitemap caching persistence using a clean `maxAge` configuration option to lower crawl footprints on backend nodes.
22
24
 
23
25
  ## Installation
24
26
 
@@ -36,10 +38,10 @@ import { getServerSitemapResponse, SitemapEntry } from 'next-advanced-sitemap';
36
38
  export async function GET() {
37
39
  const entries: SitemapEntry[] = [
38
40
  {
39
- url: 'https://fomadev.com',
41
+ url: ' https://fomadev.com ', // Auto-trimmed seamlessly in v1.0.7
40
42
  lastmod: new Date(),
41
43
  changefreq: 'daily', // Strictly typed
42
- priority: 1.0, // Auto-completed and strictly typed
44
+ priority: 1.0, // Auto-completed and strictly typed
43
45
  alternates: [
44
46
  { hreflang: 'fr', href: 'https://fomadev.com/fr' },
45
47
  { hreflang: 'en', href: 'https://fomadev.com/en' }
@@ -47,6 +49,7 @@ export async function GET() {
47
49
  },
48
50
  {
49
51
  url: 'https://fomadev.com/dashboard',
52
+ priority: 0.8,
50
53
  images: [
51
54
  {
52
55
  loc: 'https://fomadev.com/charts/analytics.png',
@@ -57,19 +60,23 @@ export async function GET() {
57
60
  },
58
61
  {
59
62
  url: 'https://fomadev.com/video-tutorial',
63
+ priority: 0.6,
60
64
  videos: [
61
65
  {
62
66
  thumbnail_loc: 'https://fomadev.com/thumbs/tutorial.jpg',
63
67
  title: 'Next.js Advanced SEO Tutorial',
64
68
  description: 'Learn how to implement advanced sitemaps in Next.js & React.',
65
- publication_date: new Date('2026-04-22') // Accepts raw Date objects smoothly
69
+ publication_date: new Date('2026-05-25') // Accepts raw Date objects smoothly
66
70
  }
67
71
  ]
68
72
  }
69
73
  ];
70
74
 
71
- // (Optional) v1.0.3: Enable autoLastmod to fill missing dates automatically
72
- return getServerSitemapResponse(entries, { autoLastmod: true });
75
+ // Enable autoLastmod and sortByPriority (v1.0.8) to optimize crawl efficiency
76
+ return getServerSitemapResponse(entries, {
77
+ autoLastmod: true,
78
+ sortByPriority: true
79
+ });
73
80
  }
74
81
  ```
75
82
 
@@ -83,6 +90,10 @@ Generates a standard Next.js `Response` object with the correct `application/xml
83
90
 
84
91
  * `autoLastmod` (boolean): If `true`, injects the current ISO date for any entry missing the `lastmod` property.
85
92
 
93
+ * `sortByPriority` (boolean): If `true`, sorts the sitemap records in a descending sequence based on their priority level (`1.0` down to `0.0`) before writing the XML stream. Items without an explicit priority fall back safely to `0.5`.
94
+
95
+ * `maxAge` (number): (Optional) Maximum lifespan duration expressed in seconds. Transforms the HTTP communication layer payload to use a rigid `public, max-age=X, must-revalidate` schema.
96
+
86
97
  ### SitemapEntry Object
87
98
 
88
99
  <table>
@@ -139,7 +150,10 @@ Generates a standard Next.js `Response` object with the correct `application/xml
139
150
 
140
151
  ## Technical Implementation
141
152
 
142
- ### Auto-Trimming & Ingestion Sanitization (v1.0.7 Update)
153
+ ### Priority Auto-Sorting
154
+ Search engine crawlers allocate a finite scanning resource quota (crawl budget) when inspecting domain properties. By default, raw database queries or collection iterations generate un-ordered XML lists, causing indexers to process trivial nodes ahead of strategic content. When `sortByPriority` is enabled, the generation engine executes an immutable descending sorting operation. Unlabeled entries smoothly receive an RFC-compliant fallback baseline score of `0.5`, allowing top-tier entries to line up at the absolute top of the index file.
155
+
156
+ ### Auto-Trimming & Ingestion Sanitization
143
157
 
144
158
  Distributed content pipelines frequently face issues with accidental leading spaces, trailing newlines, or indentation remnants introduced via headless CMS panels or Markdown document updates. To protect against application deployment errors caused by these invisible characters, the pipeline incorporates an automatic `.trim()` sanitization step. This layer cleans all input strings—including primary entries, alternative nodes, image endpoints, and video references—and passes the cleaned string directly down to the structural validation layer and the output XML stream.
145
159
 
package/dist/index.cjs CHANGED
@@ -78,6 +78,14 @@ function sanitizeAndValidateUrl(rawUrl, context) {
78
78
  }
79
79
  function generateXml(entries, options = {}) {
80
80
  const now = (/* @__PURE__ */ new Date()).toISOString();
81
+ let finalEntries = [...entries];
82
+ if (options.sortByPriority) {
83
+ finalEntries.sort((a, b) => {
84
+ const priorityA = a.priority !== void 0 ? a.priority : 0.5;
85
+ const priorityB = b.priority !== void 0 ? b.priority : 0.5;
86
+ return priorityB - priorityA;
87
+ });
88
+ }
81
89
  let xml = `<?xml version="1.0" encoding="UTF-8"?>
82
90
  `;
83
91
  xml += `<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"
@@ -90,7 +98,7 @@ function generateXml(entries, options = {}) {
90
98
  `;
91
99
  xml += ` xmlns:xhtml="http://www.w3.org/1999/xhtml">
92
100
  `;
93
- for (const entry of entries) {
101
+ for (const entry of finalEntries) {
94
102
  const cleanMainUrl = sanitizeAndValidateUrl(entry.url, "main entry");
95
103
  xml += ` <url>
96
104
  `;
@@ -190,10 +198,11 @@ function generateXml(entries, options = {}) {
190
198
  // src/index.ts
191
199
  function getServerSitemapResponse(entries, options = {}) {
192
200
  const xml = generateXml(entries, options);
201
+ const cacheControlHeader = options.maxAge !== void 0 ? `public, max-age=${options.maxAge}, must-revalidate` : "public, s-maxage=86400, stale-while-revalidate";
193
202
  return new Response(xml, {
194
203
  headers: {
195
204
  "Content-Type": "application/xml",
196
- "Cache-Control": "public, s-maxage=86400, stale-while-revalidate"
205
+ "Cache-Control": cacheControlHeader
197
206
  }
198
207
  });
199
208
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/utils/xml-escape.ts","../src/core/generator.ts"],"sourcesContent":["/* * Copyright (c) 2026 Fordi / FomaDev. \r\n * Licensed under FomaDev Public License.\r\n * See LICENSE file in the project root for full license information.\r\n */\r\n\r\nimport { SitemapEntry, SitemapOptions } from './types/sitemap.js';\r\nimport { generateXml } from './core/generator.js';\r\n\r\nexport * from './types/sitemap.js';\r\n\r\n/**\r\n * Génère une réponse HTTP compatible Next.js (App Router) avec options de configuration.\r\n * * @param entries - Liste des entrées du sitemap\r\n * @param options - Options de génération facultatives (ex: autoLastmod)\r\n * @returns Une instance de Response contenant le flux XML configuré\r\n */\r\nexport function getServerSitemapResponse(\r\n entries: SitemapEntry[], \r\n options: SitemapOptions = {}\r\n): Response {\r\n const xml = generateXml(entries, options);\r\n\r\n return new Response(xml, {\r\n headers: {\r\n 'Content-Type': 'application/xml',\r\n 'Cache-Control': 'public, s-maxage=86400, stale-while-revalidate',\r\n },\r\n });\r\n}","/* * Copyright (c) 2026 Fordi / FomaDev. \r\n * Licensed under FomaDev Public License.\r\n * See LICENSE file in the project root for full license information.\r\n */\r\n\r\n/**\r\n * Convertit les caractères spéciaux en entités XML pour éviter la corruption du fichier.\r\n * Gère : <, >, &, \", '\r\n */\r\nexport function escapeXml(unsafe: string | undefined | null): string {\r\n if (!unsafe) return '';\r\n \r\n return unsafe.replace(/[<>&\"']/g, (c) => {\r\n switch (c) {\r\n case '<': return '&lt;';\r\n case '>': return '&gt;';\r\n case '&': return '&amp;';\r\n case '\"': return '&quot;';\r\n case \"'\": return '&apos;';\r\n default: return c;\r\n }\r\n });\r\n}","/* * Copyright (c) 2026 Fordi / FomaDev. \r\n * Licensed under FomaDev Public License.\r\n * See LICENSE file in the project root for full license information.\r\n */\r\n\r\nimport { SitemapEntry, SitemapOptions } from '../types/sitemap.js';\r\nimport { escapeXml } from '../utils/xml-escape.js';\r\n\r\n/**\r\n * Nettoie et valide de manière stricte le format et la structure d'une URL.\r\n * v1.0.7 : Intégration de l'Auto-Trimming (nettoyage des espaces de début et de fin)\r\n */\r\nfunction sanitizeAndValidateUrl(rawUrl: string, context: string): string {\r\n // 1. Auto-Trimming (v1.0.7)\r\n const url = rawUrl ? rawUrl.trim() : '';\r\n\r\n // 2. Vérification rapide du protocole\r\n if (!url.startsWith('http://') && !url.startsWith('https://')) {\r\n throw new Error(\r\n `[next-advanced-sitemap] Invalid URL in ${context}: \"${url}\". URLs must start with http:// or https://`\r\n );\r\n }\r\n\r\n // 3. Sécurité v1.0.4 : Interdire strictement les espaces blancs internes non encodés\r\n if (url.includes(' ')) {\r\n throw new Error(\r\n `[next-advanced-sitemap] Malformed URL structure detected in ${context}: \"${url}\". Please verify spaces or special characters.`\r\n );\r\n }\r\n\r\n // 4. Validation fine de la structure globale via le moteur natif\r\n let isValid = false;\r\n if (typeof URL.canParse === 'function') {\r\n isValid = URL.canParse(url);\r\n } else {\r\n try {\r\n new URL(url);\r\n isValid = true;\r\n } catch {\r\n isValid = false;\r\n }\r\n }\r\n\r\n if (!isValid) {\r\n throw new Error(\r\n `[next-advanced-sitemap] Malformed URL structure detected in ${context}: \"${url}\". Please verify spaces or special characters.`\r\n );\r\n }\r\n\r\n return url; // Retourne l'URL nettoyée\r\n}\r\n\r\n/**\r\n * Génère le flux XML complet du sitemap incluant les extensions Images, Vidéos, News et Hreflang.\r\n */\r\nexport function generateXml(entries: SitemapEntry[], options: SitemapOptions = {}): string {\r\n const now = new Date().toISOString();\r\n \r\n let xml = `<?xml version=\"1.0\" encoding=\"UTF-8\"?>\\n`;\r\n xml += `<urlset xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\"\\n`;\r\n xml += ` xmlns:image=\"http://www.google.com/schemas/sitemap-image/1.1\"\\n`;\r\n xml += ` xmlns:video=\"http://www.google.com/schemas/sitemap-video/1.1\"\\n`;\r\n xml += ` xmlns:news=\"http://www.google.com/schemas/sitemap-news/0.9\"\\n`;\r\n xml += ` xmlns:xhtml=\"http://www.w3.org/1999/xhtml\">\\n`;\r\n\r\n for (const entry of entries) {\r\n // Validation et nettoyage URL principale\r\n const cleanMainUrl = sanitizeAndValidateUrl(entry.url, 'main entry');\r\n\r\n xml += ` <url>\\n`;\r\n xml += ` <loc>${escapeXml(cleanMainUrl)}</loc>\\n`;\r\n\r\n // Support Hreflang (Internationalisation)\r\n if (entry.alternates?.length) {\r\n for (const alt of entry.alternates) {\r\n const cleanAltUrl = sanitizeAndValidateUrl(alt.href, 'alternate link');\r\n xml += ` <xhtml:link rel=\"alternate\" hreflang=\"${escapeXml(alt.hreflang)}\" href=\"${escapeXml(cleanAltUrl)}\" />\\n`;\r\n }\r\n }\r\n\r\n // --- LOGIQUE AUTO-LASTMOD ---\r\n let lastmodValue = entry.lastmod;\r\n if (options.autoLastmod && !lastmodValue) {\r\n lastmodValue = now;\r\n }\r\n\r\n if (lastmodValue) {\r\n const date = lastmodValue instanceof Date ? lastmodValue.toISOString() : lastmodValue;\r\n xml += ` <lastmod>${date}</lastmod>\\n`;\r\n }\r\n\r\n if (entry.changefreq) {\r\n xml += ` <changefreq>${entry.changefreq}</changefreq>\\n`;\r\n }\r\n\r\n if (entry.priority !== undefined) {\r\n xml += ` <priority>${entry.priority.toFixed(1)}</priority>\\n`;\r\n }\r\n\r\n // Extension Images\r\n if (entry.images?.length) {\r\n for (const img of entry.images) {\r\n const cleanImgUrl = sanitizeAndValidateUrl(img.loc, 'image location');\r\n xml += ` <image:image>\\n`;\r\n xml += ` <image:loc>${escapeXml(cleanImgUrl)}</image:loc>\\n`;\r\n if (img.title) xml += ` <image:title>${escapeXml(img.title)}</image:title>\\n`;\r\n if (img.caption) xml += ` <image:caption>${escapeXml(img.caption)}</image:caption>\\n`;\r\n xml += ` </image:image>\\n`;\r\n }\r\n }\r\n\r\n // Extension Vidéos\r\n if (entry.videos?.length) {\r\n for (const vid of entry.videos) {\r\n const cleanThumbLoc = sanitizeAndValidateUrl(vid.thumbnail_loc, 'video thumbnail');\r\n const cleanContentLoc = vid.content_loc ? sanitizeAndValidateUrl(vid.content_loc, 'video content location') : undefined;\r\n const cleanPlayerLoc = vid.player_loc ? sanitizeAndValidateUrl(vid.player_loc, 'video player location') : undefined;\r\n\r\n xml += ` <video:video>\\n`;\r\n xml += ` <video:thumbnail_loc>${escapeXml(cleanThumbLoc)}</video:thumbnail_loc>\\n`;\r\n xml += ` <video:title>${escapeXml(vid.title)}</video:title>\\n`;\r\n xml += ` <video:description>${escapeXml(vid.description)}</video:description>\\n`;\r\n \r\n if (cleanContentLoc) xml += ` <video:content_loc>${escapeXml(cleanContentLoc)}</video:content_loc>\\n`;\r\n if (cleanPlayerLoc) xml += ` <video:player_loc>${escapeXml(cleanPlayerLoc)}</video:player_loc>\\n`;\r\n \r\n if (vid.publication_date) {\r\n const vDate = vid.publication_date instanceof Date ? vid.publication_date.toISOString() : vid.publication_date;\r\n xml += ` <video:publication_date>${vDate}</video:publication_date>\\n`;\r\n }\r\n xml += ` </video:video>\\n`;\r\n }\r\n }\r\n\r\n // Extension News\r\n if (entry.news) {\r\n const nDate = entry.news.publication_date instanceof Date ? entry.news.publication_date.toISOString() : entry.news.publication_date;\r\n xml += ` <news:news>\\n`;\r\n xml += ` <news:publication>\\n`;\r\n xml += ` <news:name>${escapeXml(entry.news.name)}</news:name>\\n`;\r\n xml += ` <news:language>${escapeXml(entry.news.language)}</news:language>\\n`;\r\n xml += ` </news:publication>\\n`;\r\n xml += ` <news:publication_date>${nDate}</news:publication_date>\\n`;\r\n xml += ` <news:title>${escapeXml(entry.news.title)}</news:title>\\n`;\r\n xml += ` </news:news>\\n`;\r\n }\r\n\r\n xml += ` </url>\\n`;\r\n }\r\n\r\n xml += `</urlset>`;\r\n return xml;\r\n}"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/index.ts","../src/utils/xml-escape.ts","../src/core/generator.ts"],"sourcesContent":["/* * Copyright (c) 2026 Fordi / FomaDev. \r\n * Licensed under FomaDev Public License.\r\n * See LICENSE file in the project root for full license information.\r\n */\r\n\r\nimport { SitemapEntry, SitemapOptions } from './types/sitemap.js';\r\nimport { generateXml } from './core/generator.js';\r\n\r\nexport * from './types/sitemap.js';\r\n\r\n/**\r\n * Génère une réponse HTTP compatible Next.js (App Router) avec options de configuration.\r\n * v1.0.9 : Injection dynamique et personnalisable de l'en-tête Cache-Control via l'option maxAge\r\n * * @param entries - Liste des entrées du sitemap\r\n * @param options - Options de génération et de mise en cache (ex: autoLastmod, maxAge)\r\n * @returns Une instance de Response contenant le flux XML configuré\r\n */\r\nexport function getServerSitemapResponse(\r\n entries: SitemapEntry[], \r\n options: SitemapOptions = {}\r\n): Response {\r\n const xml = generateXml(entries, options);\r\n\r\n // Détermination de la stratégie de mise en cache (v1.0.9)\r\n const cacheControlHeader = options.maxAge !== undefined\r\n ? `public, max-age=${options.maxAge}, must-revalidate`\r\n : 'public, s-maxage=86400, stale-while-revalidate';\r\n\r\n return new Response(xml, {\r\n headers: {\r\n 'Content-Type': 'application/xml',\r\n 'Cache-Control': cacheControlHeader,\r\n },\r\n });\r\n}","/* * Copyright (c) 2026 Fordi / FomaDev. \r\n * Licensed under FomaDev Public License.\r\n * See LICENSE file in the project root for full license information.\r\n */\r\n\r\n/**\r\n * Convertit les caractères spéciaux en entités XML pour éviter la corruption du fichier.\r\n * Gère : <, >, &, \", '\r\n */\r\nexport function escapeXml(unsafe: string | undefined | null): string {\r\n if (!unsafe) return '';\r\n \r\n return unsafe.replace(/[<>&\"']/g, (c) => {\r\n switch (c) {\r\n case '<': return '&lt;';\r\n case '>': return '&gt;';\r\n case '&': return '&amp;';\r\n case '\"': return '&quot;';\r\n case \"'\": return '&apos;';\r\n default: return c;\r\n }\r\n });\r\n}","/* * Copyright (c) 2026 Fordi / FomaDev. \r\n * Licensed under FomaDev Public License.\r\n * See LICENSE file in the project root for full license information.\r\n */\r\n\r\nimport { SitemapEntry, SitemapOptions } from '../types/sitemap.js';\r\nimport { escapeXml } from '../utils/xml-escape.js';\r\n\r\n/**\r\n * Nettoie et valide de manière stricte le format et la structure d'une URL.\r\n * v1.0.7 : Intégration de l'Auto-Trimming (nettoyage des espaces de début et de fin)\r\n */\r\nfunction sanitizeAndValidateUrl(rawUrl: string, context: string): string {\r\n // 1. Auto-Trimming (v1.0.7)\r\n const url = rawUrl ? rawUrl.trim() : '';\r\n\r\n // 2. Vérification rapide du protocole\r\n if (!url.startsWith('http://') && !url.startsWith('https://')) {\r\n throw new Error(\r\n `[next-advanced-sitemap] Invalid URL in ${context}: \"${url}\". URLs must start with http:// or https://`\r\n );\r\n }\r\n\r\n // 3. Sécurité v1.0.4 : Interdire strictement les espaces blancs internes non encodés\r\n if (url.includes(' ')) {\r\n throw new Error(\r\n `[next-advanced-sitemap] Malformed URL structure detected in ${context}: \"${url}\". Please verify spaces or special characters.`\r\n );\r\n }\r\n\r\n // 4. Validation fine de la structure globale via le moteur natif\r\n let isValid = false;\r\n if (typeof URL.canParse === 'function') {\r\n isValid = URL.canParse(url);\r\n } else {\r\n try {\r\n new URL(url);\r\n isValid = true;\r\n } catch {\r\n isValid = false;\r\n }\r\n }\r\n\r\n if (!isValid) {\r\n throw new Error(\r\n `[next-advanced-sitemap] Malformed URL structure detected in ${context}: \"${url}\". Please verify spaces or special characters.`\r\n );\r\n }\r\n\r\n return url; // Retourne l'URL nettoyée\r\n}\r\n\r\n/**\r\n * Génère le flux XML complet du sitemap incluant les extensions Images, Vidéos, News et Hreflang.\r\n * v1.0.8 : Intégration du tri automatique optionnel par priorité (Auto-Sorting)\r\n */\r\nexport function generateXml(entries: SitemapEntry[], options: SitemapOptions = {}): string {\r\n const now = new Date().toISOString();\r\n \r\n // Cloner le tableau d'entrées pour éviter de modifier la structure d'origine par effet de bord\r\n let finalEntries = [...entries];\r\n\r\n // Exécuter l'Auto-Sorting si l'option sortByPriority est activée\r\n if (options.sortByPriority) {\r\n finalEntries.sort((a, b) => {\r\n const priorityA = a.priority !== undefined ? (a.priority as number) : 0.5;\r\n const priorityB = b.priority !== undefined ? (b.priority as number) : 0.5;\r\n return priorityB - priorityA; // Ordre décroissant : de 1.0 à 0.0\r\n });\r\n }\r\n \r\n let xml = `<?xml version=\"1.0\" encoding=\"UTF-8\"?>\\n`;\r\n xml += `<urlset xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\"\\n`;\r\n xml += ` xmlns:image=\"http://www.google.com/schemas/sitemap-image/1.1\"\\n`;\r\n xml += ` xmlns:video=\"http://www.google.com/schemas/sitemap-video/1.1\"\\n`;\r\n xml += ` xmlns:news=\"http://www.google.com/schemas/sitemap-news/0.9\"\\n`;\r\n xml += ` xmlns:xhtml=\"http://www.w3.org/1999/xhtml\">\\n`;\r\n\r\n for (const entry of finalEntries) {\r\n // Validation et nettoyage URL principale\r\n const cleanMainUrl = sanitizeAndValidateUrl(entry.url, 'main entry');\r\n\r\n xml += ` <url>\\n`;\r\n xml += ` <loc>${escapeXml(cleanMainUrl)}</loc>\\n`;\r\n\r\n // Support Hreflang (Internationalisation)\r\n if (entry.alternates?.length) {\r\n for (const alt of entry.alternates) {\r\n const cleanAltUrl = sanitizeAndValidateUrl(alt.href, 'alternate link');\r\n xml += ` <xhtml:link rel=\"alternate\" hreflang=\"${escapeXml(alt.hreflang)}\" href=\"${escapeXml(cleanAltUrl)}\" />\\n`;\r\n }\r\n }\r\n\r\n // --- LOGIQUE AUTO-LASTMOD ---\r\n let lastmodValue = entry.lastmod;\r\n if (options.autoLastmod && !lastmodValue) {\r\n lastmodValue = now;\r\n }\r\n\r\n if (lastmodValue) {\r\n const date = lastmodValue instanceof Date ? lastmodValue.toISOString() : lastmodValue;\r\n xml += ` <lastmod>${date}</lastmod>\\n`;\r\n }\r\n\r\n if (entry.changefreq) {\r\n xml += ` <changefreq>${entry.changefreq}</changefreq>\\n`;\r\n }\r\n\r\n if (entry.priority !== undefined) {\r\n xml += ` <priority>${(entry.priority as number).toFixed(1)}</priority>\\n`;\r\n }\r\n\r\n // Extension Images\r\n if (entry.images?.length) {\r\n for (const img of entry.images) {\r\n const cleanImgUrl = sanitizeAndValidateUrl(img.loc, 'image location');\r\n xml += ` <image:image>\\n`;\r\n xml += ` <image:loc>${escapeXml(cleanImgUrl)}</image:loc>\\n`;\r\n if (img.title) xml += ` <image:title>${escapeXml(img.title)}</image:title>\\n`;\r\n if (img.caption) xml += ` <image:caption>${escapeXml(img.caption)}</image:caption>\\n`;\r\n xml += ` </image:image>\\n`;\r\n }\r\n }\r\n\r\n // Extension Vidéos\r\n if (entry.videos?.length) {\r\n for (const vid of entry.videos) {\r\n const cleanThumbLoc = sanitizeAndValidateUrl(vid.thumbnail_loc, 'video thumbnail');\r\n const cleanContentLoc = vid.content_loc ? sanitizeAndValidateUrl(vid.content_loc, 'video content location') : undefined;\r\n const cleanPlayerLoc = vid.player_loc ? sanitizeAndValidateUrl(vid.player_loc, 'video player location') : undefined;\r\n\r\n xml += ` <video:video>\\n`;\r\n xml += ` <video:thumbnail_loc>${escapeXml(cleanThumbLoc)}</video:thumbnail_loc>\\n`;\r\n xml += ` <video:title>${escapeXml(vid.title)}</video:title>\\n`;\r\n xml += ` <video:description>${escapeXml(vid.description)}</video:description>\\n`;\r\n \r\n if (cleanContentLoc) xml += ` <video:content_loc>${escapeXml(cleanContentLoc)}</video:content_loc>\\n`;\r\n if (cleanPlayerLoc) xml += ` <video:player_loc>${escapeXml(cleanPlayerLoc)}</video:player_loc>\\n`;\r\n \r\n if (vid.publication_date) {\r\n const vDate = vid.publication_date instanceof Date ? vid.publication_date.toISOString() : vid.publication_date;\r\n xml += ` <video:publication_date>${vDate}</video:publication_date>\\n`;\r\n }\r\n xml += ` </video:video>\\n`;\r\n }\r\n }\r\n\r\n // Extension News\r\n if (entry.news) {\r\n const nDate = entry.news.publication_date instanceof Date ? entry.news.publication_date.toISOString() : entry.news.publication_date;\r\n xml += ` <news:news>\\n`;\r\n xml += ` <news:publication>\\n`;\r\n xml += ` <news:name>${escapeXml(entry.news.name)}</news:name>\\n`;\r\n xml += ` <news:language>${escapeXml(entry.news.language)}</news:language>\\n`;\r\n xml += ` </news:publication>\\n`;\r\n xml += ` <news:publication_date>${nDate}</news:publication_date>\\n`;\r\n xml += ` <news:title>${escapeXml(entry.news.title)}</news:title>\\n`;\r\n xml += ` </news:news>\\n`;\r\n }\r\n\r\n xml += ` </url>\\n`;\r\n }\r\n\r\n xml += `</urlset>`;\r\n return xml;\r\n}"],"mappings":"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","names":[]}
package/dist/index.d.cts CHANGED
@@ -74,12 +74,25 @@ interface SitemapOptions {
74
74
  * qui n'ont pas de champ 'lastmod' défini.
75
75
  */
76
76
  autoLastmod?: boolean;
77
+ /**
78
+ * Si true, trie le tableau des URLs de la priorité la plus haute (1.0)
79
+ * à la plus basse (0.0) avant de lancer la génération du flux XML.
80
+ * Les entrées sans priorité héritent d'une valeur par défaut de 0.5.
81
+ */
82
+ sortByPriority?: boolean;
83
+ /**
84
+ * Durée maximale de mise en cache (TTL) exprimée en secondes.
85
+ * Si définie, l'en-tête Cache-Control prendra la forme : public, max-age=X, must-revalidate.
86
+ * Si omise, conserve la stratégie par défaut hautement performante pour CDN.
87
+ */
88
+ maxAge?: number;
77
89
  }
78
90
 
79
91
  /**
80
92
  * Génère une réponse HTTP compatible Next.js (App Router) avec options de configuration.
93
+ * v1.0.9 : Injection dynamique et personnalisable de l'en-tête Cache-Control via l'option maxAge
81
94
  * * @param entries - Liste des entrées du sitemap
82
- * @param options - Options de génération facultatives (ex: autoLastmod)
95
+ * @param options - Options de génération et de mise en cache (ex: autoLastmod, maxAge)
83
96
  * @returns Une instance de Response contenant le flux XML configuré
84
97
  */
85
98
  declare function getServerSitemapResponse(entries: SitemapEntry[], options?: SitemapOptions): Response;
package/dist/index.d.ts CHANGED
@@ -74,12 +74,25 @@ interface SitemapOptions {
74
74
  * qui n'ont pas de champ 'lastmod' défini.
75
75
  */
76
76
  autoLastmod?: boolean;
77
+ /**
78
+ * Si true, trie le tableau des URLs de la priorité la plus haute (1.0)
79
+ * à la plus basse (0.0) avant de lancer la génération du flux XML.
80
+ * Les entrées sans priorité héritent d'une valeur par défaut de 0.5.
81
+ */
82
+ sortByPriority?: boolean;
83
+ /**
84
+ * Durée maximale de mise en cache (TTL) exprimée en secondes.
85
+ * Si définie, l'en-tête Cache-Control prendra la forme : public, max-age=X, must-revalidate.
86
+ * Si omise, conserve la stratégie par défaut hautement performante pour CDN.
87
+ */
88
+ maxAge?: number;
77
89
  }
78
90
 
79
91
  /**
80
92
  * Génère une réponse HTTP compatible Next.js (App Router) avec options de configuration.
93
+ * v1.0.9 : Injection dynamique et personnalisable de l'en-tête Cache-Control via l'option maxAge
81
94
  * * @param entries - Liste des entrées du sitemap
82
- * @param options - Options de génération facultatives (ex: autoLastmod)
95
+ * @param options - Options de génération et de mise en cache (ex: autoLastmod, maxAge)
83
96
  * @returns Une instance de Response contenant le flux XML configuré
84
97
  */
85
98
  declare function getServerSitemapResponse(entries: SitemapEntry[], options?: SitemapOptions): Response;
package/dist/index.js CHANGED
@@ -52,6 +52,14 @@ function sanitizeAndValidateUrl(rawUrl, context) {
52
52
  }
53
53
  function generateXml(entries, options = {}) {
54
54
  const now = (/* @__PURE__ */ new Date()).toISOString();
55
+ let finalEntries = [...entries];
56
+ if (options.sortByPriority) {
57
+ finalEntries.sort((a, b) => {
58
+ const priorityA = a.priority !== void 0 ? a.priority : 0.5;
59
+ const priorityB = b.priority !== void 0 ? b.priority : 0.5;
60
+ return priorityB - priorityA;
61
+ });
62
+ }
55
63
  let xml = `<?xml version="1.0" encoding="UTF-8"?>
56
64
  `;
57
65
  xml += `<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"
@@ -64,7 +72,7 @@ function generateXml(entries, options = {}) {
64
72
  `;
65
73
  xml += ` xmlns:xhtml="http://www.w3.org/1999/xhtml">
66
74
  `;
67
- for (const entry of entries) {
75
+ for (const entry of finalEntries) {
68
76
  const cleanMainUrl = sanitizeAndValidateUrl(entry.url, "main entry");
69
77
  xml += ` <url>
70
78
  `;
@@ -164,10 +172,11 @@ function generateXml(entries, options = {}) {
164
172
  // src/index.ts
165
173
  function getServerSitemapResponse(entries, options = {}) {
166
174
  const xml = generateXml(entries, options);
175
+ const cacheControlHeader = options.maxAge !== void 0 ? `public, max-age=${options.maxAge}, must-revalidate` : "public, s-maxage=86400, stale-while-revalidate";
167
176
  return new Response(xml, {
168
177
  headers: {
169
178
  "Content-Type": "application/xml",
170
- "Cache-Control": "public, s-maxage=86400, stale-while-revalidate"
179
+ "Cache-Control": cacheControlHeader
171
180
  }
172
181
  });
173
182
  }
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/utils/xml-escape.ts","../src/core/generator.ts","../src/index.ts"],"sourcesContent":["/* * Copyright (c) 2026 Fordi / FomaDev. \r\n * Licensed under FomaDev Public License.\r\n * See LICENSE file in the project root for full license information.\r\n */\r\n\r\n/**\r\n * Convertit les caractères spéciaux en entités XML pour éviter la corruption du fichier.\r\n * Gère : <, >, &, \", '\r\n */\r\nexport function escapeXml(unsafe: string | undefined | null): string {\r\n if (!unsafe) return '';\r\n \r\n return unsafe.replace(/[<>&\"']/g, (c) => {\r\n switch (c) {\r\n case '<': return '&lt;';\r\n case '>': return '&gt;';\r\n case '&': return '&amp;';\r\n case '\"': return '&quot;';\r\n case \"'\": return '&apos;';\r\n default: return c;\r\n }\r\n });\r\n}","/* * Copyright (c) 2026 Fordi / FomaDev. \r\n * Licensed under FomaDev Public License.\r\n * See LICENSE file in the project root for full license information.\r\n */\r\n\r\nimport { SitemapEntry, SitemapOptions } from '../types/sitemap.js';\r\nimport { escapeXml } from '../utils/xml-escape.js';\r\n\r\n/**\r\n * Nettoie et valide de manière stricte le format et la structure d'une URL.\r\n * v1.0.7 : Intégration de l'Auto-Trimming (nettoyage des espaces de début et de fin)\r\n */\r\nfunction sanitizeAndValidateUrl(rawUrl: string, context: string): string {\r\n // 1. Auto-Trimming (v1.0.7)\r\n const url = rawUrl ? rawUrl.trim() : '';\r\n\r\n // 2. Vérification rapide du protocole\r\n if (!url.startsWith('http://') && !url.startsWith('https://')) {\r\n throw new Error(\r\n `[next-advanced-sitemap] Invalid URL in ${context}: \"${url}\". URLs must start with http:// or https://`\r\n );\r\n }\r\n\r\n // 3. Sécurité v1.0.4 : Interdire strictement les espaces blancs internes non encodés\r\n if (url.includes(' ')) {\r\n throw new Error(\r\n `[next-advanced-sitemap] Malformed URL structure detected in ${context}: \"${url}\". Please verify spaces or special characters.`\r\n );\r\n }\r\n\r\n // 4. Validation fine de la structure globale via le moteur natif\r\n let isValid = false;\r\n if (typeof URL.canParse === 'function') {\r\n isValid = URL.canParse(url);\r\n } else {\r\n try {\r\n new URL(url);\r\n isValid = true;\r\n } catch {\r\n isValid = false;\r\n }\r\n }\r\n\r\n if (!isValid) {\r\n throw new Error(\r\n `[next-advanced-sitemap] Malformed URL structure detected in ${context}: \"${url}\". Please verify spaces or special characters.`\r\n );\r\n }\r\n\r\n return url; // Retourne l'URL nettoyée\r\n}\r\n\r\n/**\r\n * Génère le flux XML complet du sitemap incluant les extensions Images, Vidéos, News et Hreflang.\r\n */\r\nexport function generateXml(entries: SitemapEntry[], options: SitemapOptions = {}): string {\r\n const now = new Date().toISOString();\r\n \r\n let xml = `<?xml version=\"1.0\" encoding=\"UTF-8\"?>\\n`;\r\n xml += `<urlset xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\"\\n`;\r\n xml += ` xmlns:image=\"http://www.google.com/schemas/sitemap-image/1.1\"\\n`;\r\n xml += ` xmlns:video=\"http://www.google.com/schemas/sitemap-video/1.1\"\\n`;\r\n xml += ` xmlns:news=\"http://www.google.com/schemas/sitemap-news/0.9\"\\n`;\r\n xml += ` xmlns:xhtml=\"http://www.w3.org/1999/xhtml\">\\n`;\r\n\r\n for (const entry of entries) {\r\n // Validation et nettoyage URL principale\r\n const cleanMainUrl = sanitizeAndValidateUrl(entry.url, 'main entry');\r\n\r\n xml += ` <url>\\n`;\r\n xml += ` <loc>${escapeXml(cleanMainUrl)}</loc>\\n`;\r\n\r\n // Support Hreflang (Internationalisation)\r\n if (entry.alternates?.length) {\r\n for (const alt of entry.alternates) {\r\n const cleanAltUrl = sanitizeAndValidateUrl(alt.href, 'alternate link');\r\n xml += ` <xhtml:link rel=\"alternate\" hreflang=\"${escapeXml(alt.hreflang)}\" href=\"${escapeXml(cleanAltUrl)}\" />\\n`;\r\n }\r\n }\r\n\r\n // --- LOGIQUE AUTO-LASTMOD ---\r\n let lastmodValue = entry.lastmod;\r\n if (options.autoLastmod && !lastmodValue) {\r\n lastmodValue = now;\r\n }\r\n\r\n if (lastmodValue) {\r\n const date = lastmodValue instanceof Date ? lastmodValue.toISOString() : lastmodValue;\r\n xml += ` <lastmod>${date}</lastmod>\\n`;\r\n }\r\n\r\n if (entry.changefreq) {\r\n xml += ` <changefreq>${entry.changefreq}</changefreq>\\n`;\r\n }\r\n\r\n if (entry.priority !== undefined) {\r\n xml += ` <priority>${entry.priority.toFixed(1)}</priority>\\n`;\r\n }\r\n\r\n // Extension Images\r\n if (entry.images?.length) {\r\n for (const img of entry.images) {\r\n const cleanImgUrl = sanitizeAndValidateUrl(img.loc, 'image location');\r\n xml += ` <image:image>\\n`;\r\n xml += ` <image:loc>${escapeXml(cleanImgUrl)}</image:loc>\\n`;\r\n if (img.title) xml += ` <image:title>${escapeXml(img.title)}</image:title>\\n`;\r\n if (img.caption) xml += ` <image:caption>${escapeXml(img.caption)}</image:caption>\\n`;\r\n xml += ` </image:image>\\n`;\r\n }\r\n }\r\n\r\n // Extension Vidéos\r\n if (entry.videos?.length) {\r\n for (const vid of entry.videos) {\r\n const cleanThumbLoc = sanitizeAndValidateUrl(vid.thumbnail_loc, 'video thumbnail');\r\n const cleanContentLoc = vid.content_loc ? sanitizeAndValidateUrl(vid.content_loc, 'video content location') : undefined;\r\n const cleanPlayerLoc = vid.player_loc ? sanitizeAndValidateUrl(vid.player_loc, 'video player location') : undefined;\r\n\r\n xml += ` <video:video>\\n`;\r\n xml += ` <video:thumbnail_loc>${escapeXml(cleanThumbLoc)}</video:thumbnail_loc>\\n`;\r\n xml += ` <video:title>${escapeXml(vid.title)}</video:title>\\n`;\r\n xml += ` <video:description>${escapeXml(vid.description)}</video:description>\\n`;\r\n \r\n if (cleanContentLoc) xml += ` <video:content_loc>${escapeXml(cleanContentLoc)}</video:content_loc>\\n`;\r\n if (cleanPlayerLoc) xml += ` <video:player_loc>${escapeXml(cleanPlayerLoc)}</video:player_loc>\\n`;\r\n \r\n if (vid.publication_date) {\r\n const vDate = vid.publication_date instanceof Date ? vid.publication_date.toISOString() : vid.publication_date;\r\n xml += ` <video:publication_date>${vDate}</video:publication_date>\\n`;\r\n }\r\n xml += ` </video:video>\\n`;\r\n }\r\n }\r\n\r\n // Extension News\r\n if (entry.news) {\r\n const nDate = entry.news.publication_date instanceof Date ? entry.news.publication_date.toISOString() : entry.news.publication_date;\r\n xml += ` <news:news>\\n`;\r\n xml += ` <news:publication>\\n`;\r\n xml += ` <news:name>${escapeXml(entry.news.name)}</news:name>\\n`;\r\n xml += ` <news:language>${escapeXml(entry.news.language)}</news:language>\\n`;\r\n xml += ` </news:publication>\\n`;\r\n xml += ` <news:publication_date>${nDate}</news:publication_date>\\n`;\r\n xml += ` <news:title>${escapeXml(entry.news.title)}</news:title>\\n`;\r\n xml += ` </news:news>\\n`;\r\n }\r\n\r\n xml += ` </url>\\n`;\r\n }\r\n\r\n xml += `</urlset>`;\r\n return xml;\r\n}","/* * Copyright (c) 2026 Fordi / FomaDev. \r\n * Licensed under FomaDev Public License.\r\n * See LICENSE file in the project root for full license information.\r\n */\r\n\r\nimport { SitemapEntry, SitemapOptions } from './types/sitemap.js';\r\nimport { generateXml } from './core/generator.js';\r\n\r\nexport * from './types/sitemap.js';\r\n\r\n/**\r\n * Génère une réponse HTTP compatible Next.js (App Router) avec options de configuration.\r\n * * @param entries - Liste des entrées du sitemap\r\n * @param options - Options de génération facultatives (ex: autoLastmod)\r\n * @returns Une instance de Response contenant le flux XML configuré\r\n */\r\nexport function getServerSitemapResponse(\r\n entries: SitemapEntry[], \r\n options: SitemapOptions = {}\r\n): Response {\r\n const xml = generateXml(entries, options);\r\n\r\n return new Response(xml, {\r\n headers: {\r\n 'Content-Type': 'application/xml',\r\n 'Cache-Control': 'public, s-maxage=86400, stale-while-revalidate',\r\n },\r\n });\r\n}"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/utils/xml-escape.ts","../src/core/generator.ts","../src/index.ts"],"sourcesContent":["/* * Copyright (c) 2026 Fordi / FomaDev. \r\n * Licensed under FomaDev Public License.\r\n * See LICENSE file in the project root for full license information.\r\n */\r\n\r\n/**\r\n * Convertit les caractères spéciaux en entités XML pour éviter la corruption du fichier.\r\n * Gère : <, >, &, \", '\r\n */\r\nexport function escapeXml(unsafe: string | undefined | null): string {\r\n if (!unsafe) return '';\r\n \r\n return unsafe.replace(/[<>&\"']/g, (c) => {\r\n switch (c) {\r\n case '<': return '&lt;';\r\n case '>': return '&gt;';\r\n case '&': return '&amp;';\r\n case '\"': return '&quot;';\r\n case \"'\": return '&apos;';\r\n default: return c;\r\n }\r\n });\r\n}","/* * Copyright (c) 2026 Fordi / FomaDev. \r\n * Licensed under FomaDev Public License.\r\n * See LICENSE file in the project root for full license information.\r\n */\r\n\r\nimport { SitemapEntry, SitemapOptions } from '../types/sitemap.js';\r\nimport { escapeXml } from '../utils/xml-escape.js';\r\n\r\n/**\r\n * Nettoie et valide de manière stricte le format et la structure d'une URL.\r\n * v1.0.7 : Intégration de l'Auto-Trimming (nettoyage des espaces de début et de fin)\r\n */\r\nfunction sanitizeAndValidateUrl(rawUrl: string, context: string): string {\r\n // 1. Auto-Trimming (v1.0.7)\r\n const url = rawUrl ? rawUrl.trim() : '';\r\n\r\n // 2. Vérification rapide du protocole\r\n if (!url.startsWith('http://') && !url.startsWith('https://')) {\r\n throw new Error(\r\n `[next-advanced-sitemap] Invalid URL in ${context}: \"${url}\". URLs must start with http:// or https://`\r\n );\r\n }\r\n\r\n // 3. Sécurité v1.0.4 : Interdire strictement les espaces blancs internes non encodés\r\n if (url.includes(' ')) {\r\n throw new Error(\r\n `[next-advanced-sitemap] Malformed URL structure detected in ${context}: \"${url}\". Please verify spaces or special characters.`\r\n );\r\n }\r\n\r\n // 4. Validation fine de la structure globale via le moteur natif\r\n let isValid = false;\r\n if (typeof URL.canParse === 'function') {\r\n isValid = URL.canParse(url);\r\n } else {\r\n try {\r\n new URL(url);\r\n isValid = true;\r\n } catch {\r\n isValid = false;\r\n }\r\n }\r\n\r\n if (!isValid) {\r\n throw new Error(\r\n `[next-advanced-sitemap] Malformed URL structure detected in ${context}: \"${url}\". Please verify spaces or special characters.`\r\n );\r\n }\r\n\r\n return url; // Retourne l'URL nettoyée\r\n}\r\n\r\n/**\r\n * Génère le flux XML complet du sitemap incluant les extensions Images, Vidéos, News et Hreflang.\r\n * v1.0.8 : Intégration du tri automatique optionnel par priorité (Auto-Sorting)\r\n */\r\nexport function generateXml(entries: SitemapEntry[], options: SitemapOptions = {}): string {\r\n const now = new Date().toISOString();\r\n \r\n // Cloner le tableau d'entrées pour éviter de modifier la structure d'origine par effet de bord\r\n let finalEntries = [...entries];\r\n\r\n // Exécuter l'Auto-Sorting si l'option sortByPriority est activée\r\n if (options.sortByPriority) {\r\n finalEntries.sort((a, b) => {\r\n const priorityA = a.priority !== undefined ? (a.priority as number) : 0.5;\r\n const priorityB = b.priority !== undefined ? (b.priority as number) : 0.5;\r\n return priorityB - priorityA; // Ordre décroissant : de 1.0 à 0.0\r\n });\r\n }\r\n \r\n let xml = `<?xml version=\"1.0\" encoding=\"UTF-8\"?>\\n`;\r\n xml += `<urlset xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\"\\n`;\r\n xml += ` xmlns:image=\"http://www.google.com/schemas/sitemap-image/1.1\"\\n`;\r\n xml += ` xmlns:video=\"http://www.google.com/schemas/sitemap-video/1.1\"\\n`;\r\n xml += ` xmlns:news=\"http://www.google.com/schemas/sitemap-news/0.9\"\\n`;\r\n xml += ` xmlns:xhtml=\"http://www.w3.org/1999/xhtml\">\\n`;\r\n\r\n for (const entry of finalEntries) {\r\n // Validation et nettoyage URL principale\r\n const cleanMainUrl = sanitizeAndValidateUrl(entry.url, 'main entry');\r\n\r\n xml += ` <url>\\n`;\r\n xml += ` <loc>${escapeXml(cleanMainUrl)}</loc>\\n`;\r\n\r\n // Support Hreflang (Internationalisation)\r\n if (entry.alternates?.length) {\r\n for (const alt of entry.alternates) {\r\n const cleanAltUrl = sanitizeAndValidateUrl(alt.href, 'alternate link');\r\n xml += ` <xhtml:link rel=\"alternate\" hreflang=\"${escapeXml(alt.hreflang)}\" href=\"${escapeXml(cleanAltUrl)}\" />\\n`;\r\n }\r\n }\r\n\r\n // --- LOGIQUE AUTO-LASTMOD ---\r\n let lastmodValue = entry.lastmod;\r\n if (options.autoLastmod && !lastmodValue) {\r\n lastmodValue = now;\r\n }\r\n\r\n if (lastmodValue) {\r\n const date = lastmodValue instanceof Date ? lastmodValue.toISOString() : lastmodValue;\r\n xml += ` <lastmod>${date}</lastmod>\\n`;\r\n }\r\n\r\n if (entry.changefreq) {\r\n xml += ` <changefreq>${entry.changefreq}</changefreq>\\n`;\r\n }\r\n\r\n if (entry.priority !== undefined) {\r\n xml += ` <priority>${(entry.priority as number).toFixed(1)}</priority>\\n`;\r\n }\r\n\r\n // Extension Images\r\n if (entry.images?.length) {\r\n for (const img of entry.images) {\r\n const cleanImgUrl = sanitizeAndValidateUrl(img.loc, 'image location');\r\n xml += ` <image:image>\\n`;\r\n xml += ` <image:loc>${escapeXml(cleanImgUrl)}</image:loc>\\n`;\r\n if (img.title) xml += ` <image:title>${escapeXml(img.title)}</image:title>\\n`;\r\n if (img.caption) xml += ` <image:caption>${escapeXml(img.caption)}</image:caption>\\n`;\r\n xml += ` </image:image>\\n`;\r\n }\r\n }\r\n\r\n // Extension Vidéos\r\n if (entry.videos?.length) {\r\n for (const vid of entry.videos) {\r\n const cleanThumbLoc = sanitizeAndValidateUrl(vid.thumbnail_loc, 'video thumbnail');\r\n const cleanContentLoc = vid.content_loc ? sanitizeAndValidateUrl(vid.content_loc, 'video content location') : undefined;\r\n const cleanPlayerLoc = vid.player_loc ? sanitizeAndValidateUrl(vid.player_loc, 'video player location') : undefined;\r\n\r\n xml += ` <video:video>\\n`;\r\n xml += ` <video:thumbnail_loc>${escapeXml(cleanThumbLoc)}</video:thumbnail_loc>\\n`;\r\n xml += ` <video:title>${escapeXml(vid.title)}</video:title>\\n`;\r\n xml += ` <video:description>${escapeXml(vid.description)}</video:description>\\n`;\r\n \r\n if (cleanContentLoc) xml += ` <video:content_loc>${escapeXml(cleanContentLoc)}</video:content_loc>\\n`;\r\n if (cleanPlayerLoc) xml += ` <video:player_loc>${escapeXml(cleanPlayerLoc)}</video:player_loc>\\n`;\r\n \r\n if (vid.publication_date) {\r\n const vDate = vid.publication_date instanceof Date ? vid.publication_date.toISOString() : vid.publication_date;\r\n xml += ` <video:publication_date>${vDate}</video:publication_date>\\n`;\r\n }\r\n xml += ` </video:video>\\n`;\r\n }\r\n }\r\n\r\n // Extension News\r\n if (entry.news) {\r\n const nDate = entry.news.publication_date instanceof Date ? entry.news.publication_date.toISOString() : entry.news.publication_date;\r\n xml += ` <news:news>\\n`;\r\n xml += ` <news:publication>\\n`;\r\n xml += ` <news:name>${escapeXml(entry.news.name)}</news:name>\\n`;\r\n xml += ` <news:language>${escapeXml(entry.news.language)}</news:language>\\n`;\r\n xml += ` </news:publication>\\n`;\r\n xml += ` <news:publication_date>${nDate}</news:publication_date>\\n`;\r\n xml += ` <news:title>${escapeXml(entry.news.title)}</news:title>\\n`;\r\n xml += ` </news:news>\\n`;\r\n }\r\n\r\n xml += ` </url>\\n`;\r\n }\r\n\r\n xml += `</urlset>`;\r\n return xml;\r\n}","/* * Copyright (c) 2026 Fordi / FomaDev. \r\n * Licensed under FomaDev Public License.\r\n * See LICENSE file in the project root for full license information.\r\n */\r\n\r\nimport { SitemapEntry, SitemapOptions } from './types/sitemap.js';\r\nimport { generateXml } from './core/generator.js';\r\n\r\nexport * from './types/sitemap.js';\r\n\r\n/**\r\n * Génère une réponse HTTP compatible Next.js (App Router) avec options de configuration.\r\n * v1.0.9 : Injection dynamique et personnalisable de l'en-tête Cache-Control via l'option maxAge\r\n * * @param entries - Liste des entrées du sitemap\r\n * @param options - Options de génération et de mise en cache (ex: autoLastmod, maxAge)\r\n * @returns Une instance de Response contenant le flux XML configuré\r\n */\r\nexport function getServerSitemapResponse(\r\n entries: SitemapEntry[], \r\n options: SitemapOptions = {}\r\n): Response {\r\n const xml = generateXml(entries, options);\r\n\r\n // Détermination de la stratégie de mise en cache (v1.0.9)\r\n const cacheControlHeader = options.maxAge !== undefined\r\n ? `public, max-age=${options.maxAge}, must-revalidate`\r\n : 'public, s-maxage=86400, stale-while-revalidate';\r\n\r\n return new Response(xml, {\r\n headers: {\r\n 'Content-Type': 'application/xml',\r\n 'Cache-Control': cacheControlHeader,\r\n },\r\n });\r\n}"],"mappings":"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","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "next-advanced-sitemap",
3
- "version": "1.0.7",
3
+ "version": "1.0.9",
4
4
  "type": "module",
5
5
  "description": "Advanced sitemap generator for Next.js. Powerful support for Google Images, Video, News, and Hreflang (multilingual). Type-safe, zero-dependency, and built for App Router.",
6
6
  "main": "./dist/index.cjs",