next-advanced-sitemap 1.0.9 → 1.1.1
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 +31 -13
- package/dist/index.cjs +13 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +6 -2
- package/dist/index.d.ts +6 -2
- package/dist/index.js +13 -0
- package/dist/index.js.map +1 -1
- package/package.json +11 -2
package/README.md
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
# next-advanced-sitemap
|
|
2
2
|
|
|
3
|
+
[](LICENSE)
|
|
4
|
+

|
|
5
|
+
|
|
3
6
|
A robust and type-safe sitemap generator for Next.js (App Router). This library extends standard sitemap capabilities by providing native support for Google-specific metadata including Images, Videos, News, and Internationalization (Hreflang).
|
|
4
7
|
|
|
5
8
|
## Overview
|
|
@@ -10,6 +13,7 @@ While Next.js provides a built-in `MetadataRoute.Sitemap` utility, it currently
|
|
|
10
13
|
|
|
11
14
|
- **Google Images Support**: Index visual assets such as dashboard charts, infographics, and banners.
|
|
12
15
|
- **Google Video Support**: Improve search visibility for video content with thumbnail and description metadata.
|
|
16
|
+
- **Google Video Live Streaming (v1.1.1)**: Native injection of the `<video:live>` parameter to flag real-time broadcasts and instantly trigger red **LIVE** badges on Google SERP matrices.
|
|
13
17
|
- **Google News Support**: Comply with Google News requirements including publication names and dates.
|
|
14
18
|
- **Internationalization**: Seamless integration of `xhtml:link` tags for Hreflang and multi-regional SEO.
|
|
15
19
|
- **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.
|
|
@@ -21,6 +25,7 @@ While Next.js provides a built-in `MetadataRoute.Sitemap` utility, it currently
|
|
|
21
25
|
- **Advanced XML Escaping (v1.0.2)**: Enhanced processor to handle complex special characters (`&`, `"`, `'`, `<`, `>`) in SEO metadata, ensuring XML integrity.
|
|
22
26
|
- **Developer Experience**: Fully typed with TypeScript, zero external dependencies, and optimized for Next.js Route Handlers.
|
|
23
27
|
- **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.
|
|
28
|
+
- **Local SEO & Image Licensing (v1.1.0)**: Enhanced Google Images schema integration with full support for `geo_location` and programmatic `license` badges to protect visual assets and boost image search CTR.
|
|
24
29
|
|
|
25
30
|
## Installation
|
|
26
31
|
|
|
@@ -48,25 +53,28 @@ export async function GET() {
|
|
|
48
53
|
]
|
|
49
54
|
},
|
|
50
55
|
{
|
|
51
|
-
url: 'https://fomadev.com/
|
|
52
|
-
priority: 0.
|
|
53
|
-
|
|
56
|
+
url: 'https://fomadev.com/live-stream',
|
|
57
|
+
priority: 0.9,
|
|
58
|
+
videos: [
|
|
54
59
|
{
|
|
55
|
-
|
|
56
|
-
title: '
|
|
57
|
-
|
|
60
|
+
thumbnail_loc: 'https://fomadev.com/thumbs/live.jpg',
|
|
61
|
+
title: 'FomaDev Live Tech Session',
|
|
62
|
+
description: 'Building production-grade packages with Next.js.',
|
|
63
|
+
publication_date: new Date(),
|
|
64
|
+
live: 'yes' // v1.1.1: Triggers the official Google LIVE badge on SERP
|
|
58
65
|
}
|
|
59
66
|
]
|
|
60
67
|
},
|
|
61
68
|
{
|
|
62
|
-
url: 'https://fomadev.com/
|
|
63
|
-
priority: 0.
|
|
64
|
-
|
|
69
|
+
url: 'https://fomadev.com/dashboard',
|
|
70
|
+
priority: 0.8,
|
|
71
|
+
images: [
|
|
65
72
|
{
|
|
66
|
-
|
|
67
|
-
title: '
|
|
68
|
-
|
|
69
|
-
|
|
73
|
+
loc: 'https://fomadev.com/charts/analytics.png',
|
|
74
|
+
title: 'Growth Analytics Chart',
|
|
75
|
+
caption: 'Visual representation of monthly user growth.',
|
|
76
|
+
geo_location: 'Kinshasa, Democratic Republic of the Congo', // v1.1.0 Local SEO
|
|
77
|
+
license: 'https://fomadev.com/terms/licensing' // v1.1.0 Badging
|
|
70
78
|
}
|
|
71
79
|
]
|
|
72
80
|
}
|
|
@@ -145,6 +153,16 @@ Generates a standard Next.js `Response` object with the correct `application/xml
|
|
|
145
153
|
<td class="type-label">SitemapAlternate[]</td>
|
|
146
154
|
<td>(Optional) Regional alternate URLs (Hreflang).</td>
|
|
147
155
|
</tr>
|
|
156
|
+
<tr>
|
|
157
|
+
<td><code>geo_location</code></td>
|
|
158
|
+
<td class="type-label">string</td>
|
|
159
|
+
<td>(Optional) Geographic location string of the image (e.g., "Kinshasa, DRC").</td>
|
|
160
|
+
</tr>
|
|
161
|
+
<tr>
|
|
162
|
+
<td><code>license</code></td>
|
|
163
|
+
<td class="type-label">string</td>
|
|
164
|
+
<td>(Optional) Valid HTTP/HTTPS URL addressing the licensing rights or usage terms of the image asset.</td>
|
|
165
|
+
</tr>
|
|
148
166
|
</tbody>
|
|
149
167
|
</table>
|
|
150
168
|
|
package/dist/index.cjs
CHANGED
|
@@ -139,6 +139,15 @@ function generateXml(entries, options = {}) {
|
|
|
139
139
|
`;
|
|
140
140
|
if (img.caption) xml += ` <image:caption>${escapeXml(img.caption)}</image:caption>
|
|
141
141
|
`;
|
|
142
|
+
if (img.geo_location) {
|
|
143
|
+
xml += ` <image:geo_location>${escapeXml(img.geo_location)}</image:geo_location>
|
|
144
|
+
`;
|
|
145
|
+
}
|
|
146
|
+
if (img.license) {
|
|
147
|
+
const cleanLicenseUrl = sanitizeAndValidateUrl(img.license, "image license URL");
|
|
148
|
+
xml += ` <image:license>${escapeXml(cleanLicenseUrl)}</image:license>
|
|
149
|
+
`;
|
|
150
|
+
}
|
|
142
151
|
xml += ` </image:image>
|
|
143
152
|
`;
|
|
144
153
|
}
|
|
@@ -163,6 +172,10 @@ function generateXml(entries, options = {}) {
|
|
|
163
172
|
if (vid.publication_date) {
|
|
164
173
|
const vDate = vid.publication_date instanceof Date ? vid.publication_date.toISOString() : vid.publication_date;
|
|
165
174
|
xml += ` <video:publication_date>${vDate}</video:publication_date>
|
|
175
|
+
`;
|
|
176
|
+
}
|
|
177
|
+
if (vid.live) {
|
|
178
|
+
xml += ` <video:live>${vid.live}</video:live>
|
|
166
179
|
`;
|
|
167
180
|
}
|
|
168
181
|
xml += ` </video:video>
|
package/dist/index.cjs.map
CHANGED
|
@@ -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 * 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 '<';\r\n case '>': return '>';\r\n case '&': return '&';\r\n case '\"': return '"';\r\n case \"'\": return ''';\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":[]}
|
|
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 '<';\r\n case '>': return '>';\r\n case '&': return '&';\r\n case '\"': return '"';\r\n case \"'\": return ''';\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 const url = rawUrl ? rawUrl.trim() : '';\r\n\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 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 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;\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.1.0 : Prise en charge des attributs étendus <image:geo_location> et <image:license>\r\n */\r\nexport function generateXml(entries: SitemapEntry[], options: SitemapOptions = {}): string {\r\n const now = new Date().toISOString();\r\n let finalEntries = [...entries];\r\n\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;\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 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 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 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 - enrichie en v1.1.0\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 \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 \r\n // SEO Local v1.1.0\r\n if (img.geo_location) {\r\n xml += ` <image:geo_location>${escapeXml(img.geo_location)}</image:geo_location>\\n`;\r\n }\r\n \r\n // Gestion des Licences Google Images v1.1.0\r\n if (img.license) {\r\n const cleanLicenseUrl = sanitizeAndValidateUrl(img.license, 'image license URL');\r\n xml += ` <image:license>${escapeXml(cleanLicenseUrl)}</image:license>\\n`;\r\n }\r\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\r\n // Support du Live Streaming v1.1.1\r\n if (vid.live) {\r\n xml += ` <video:live>${vid.live}</video:live>\\n`;\r\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
|
@@ -17,14 +17,16 @@ interface SitemapAlternate {
|
|
|
17
17
|
href: string;
|
|
18
18
|
}
|
|
19
19
|
/**
|
|
20
|
-
* Interface pour les images dans le sitemap
|
|
20
|
+
* Interface pour les images dans le sitemap avec support SEO Local et Licences (v1.1.0)
|
|
21
21
|
* @see https://developers.google.com/search/docs/crawling-indexing/sitemaps/image-sitemaps
|
|
22
22
|
*/
|
|
23
23
|
interface SitemapImage {
|
|
24
24
|
loc: string;
|
|
25
25
|
caption?: string;
|
|
26
26
|
title?: string;
|
|
27
|
-
|
|
27
|
+
/** (Optional) v1.1.0: Description géographique de l'image (ex: "Kinshasa, DRC"). */
|
|
28
|
+
geo_location?: string;
|
|
29
|
+
/** (Optional) v1.1.0: URL pointant vers les conditions d'utilisation ou le contrat de licence de l'image. */
|
|
28
30
|
license?: string;
|
|
29
31
|
}
|
|
30
32
|
/**
|
|
@@ -41,6 +43,8 @@ interface SitemapVideo {
|
|
|
41
43
|
view_count?: number;
|
|
42
44
|
publication_date?: Date | string;
|
|
43
45
|
family_friendly?: 'yes' | 'no';
|
|
46
|
+
/** (Optional) v1.1.1: Indique si la vidéo est une diffusion en direct ('yes' ou 'no'). */
|
|
47
|
+
live?: 'yes' | 'no';
|
|
44
48
|
}
|
|
45
49
|
/**
|
|
46
50
|
* Interface pour Google News
|
package/dist/index.d.ts
CHANGED
|
@@ -17,14 +17,16 @@ interface SitemapAlternate {
|
|
|
17
17
|
href: string;
|
|
18
18
|
}
|
|
19
19
|
/**
|
|
20
|
-
* Interface pour les images dans le sitemap
|
|
20
|
+
* Interface pour les images dans le sitemap avec support SEO Local et Licences (v1.1.0)
|
|
21
21
|
* @see https://developers.google.com/search/docs/crawling-indexing/sitemaps/image-sitemaps
|
|
22
22
|
*/
|
|
23
23
|
interface SitemapImage {
|
|
24
24
|
loc: string;
|
|
25
25
|
caption?: string;
|
|
26
26
|
title?: string;
|
|
27
|
-
|
|
27
|
+
/** (Optional) v1.1.0: Description géographique de l'image (ex: "Kinshasa, DRC"). */
|
|
28
|
+
geo_location?: string;
|
|
29
|
+
/** (Optional) v1.1.0: URL pointant vers les conditions d'utilisation ou le contrat de licence de l'image. */
|
|
28
30
|
license?: string;
|
|
29
31
|
}
|
|
30
32
|
/**
|
|
@@ -41,6 +43,8 @@ interface SitemapVideo {
|
|
|
41
43
|
view_count?: number;
|
|
42
44
|
publication_date?: Date | string;
|
|
43
45
|
family_friendly?: 'yes' | 'no';
|
|
46
|
+
/** (Optional) v1.1.1: Indique si la vidéo est une diffusion en direct ('yes' ou 'no'). */
|
|
47
|
+
live?: 'yes' | 'no';
|
|
44
48
|
}
|
|
45
49
|
/**
|
|
46
50
|
* Interface pour Google News
|
package/dist/index.js
CHANGED
|
@@ -113,6 +113,15 @@ function generateXml(entries, options = {}) {
|
|
|
113
113
|
`;
|
|
114
114
|
if (img.caption) xml += ` <image:caption>${escapeXml(img.caption)}</image:caption>
|
|
115
115
|
`;
|
|
116
|
+
if (img.geo_location) {
|
|
117
|
+
xml += ` <image:geo_location>${escapeXml(img.geo_location)}</image:geo_location>
|
|
118
|
+
`;
|
|
119
|
+
}
|
|
120
|
+
if (img.license) {
|
|
121
|
+
const cleanLicenseUrl = sanitizeAndValidateUrl(img.license, "image license URL");
|
|
122
|
+
xml += ` <image:license>${escapeXml(cleanLicenseUrl)}</image:license>
|
|
123
|
+
`;
|
|
124
|
+
}
|
|
116
125
|
xml += ` </image:image>
|
|
117
126
|
`;
|
|
118
127
|
}
|
|
@@ -137,6 +146,10 @@ function generateXml(entries, options = {}) {
|
|
|
137
146
|
if (vid.publication_date) {
|
|
138
147
|
const vDate = vid.publication_date instanceof Date ? vid.publication_date.toISOString() : vid.publication_date;
|
|
139
148
|
xml += ` <video:publication_date>${vDate}</video:publication_date>
|
|
149
|
+
`;
|
|
150
|
+
}
|
|
151
|
+
if (vid.live) {
|
|
152
|
+
xml += ` <video:live>${vid.live}</video:live>
|
|
140
153
|
`;
|
|
141
154
|
}
|
|
142
155
|
xml += ` </video:video>
|
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 '<';\r\n case '>': return '>';\r\n case '&': return '&';\r\n case '\"': return '"';\r\n case \"'\": return ''';\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":[]}
|
|
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 '<';\r\n case '>': return '>';\r\n case '&': return '&';\r\n case '\"': return '"';\r\n case \"'\": return ''';\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 const url = rawUrl ? rawUrl.trim() : '';\r\n\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 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 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;\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.1.0 : Prise en charge des attributs étendus <image:geo_location> et <image:license>\r\n */\r\nexport function generateXml(entries: SitemapEntry[], options: SitemapOptions = {}): string {\r\n const now = new Date().toISOString();\r\n let finalEntries = [...entries];\r\n\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;\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 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 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 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 - enrichie en v1.1.0\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 \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 \r\n // SEO Local v1.1.0\r\n if (img.geo_location) {\r\n xml += ` <image:geo_location>${escapeXml(img.geo_location)}</image:geo_location>\\n`;\r\n }\r\n \r\n // Gestion des Licences Google Images v1.1.0\r\n if (img.license) {\r\n const cleanLicenseUrl = sanitizeAndValidateUrl(img.license, 'image license URL');\r\n xml += ` <image:license>${escapeXml(cleanLicenseUrl)}</image:license>\\n`;\r\n }\r\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\r\n // Support du Live Streaming v1.1.1\r\n if (vid.live) {\r\n xml += ` <video:live>${vid.live}</video:live>\\n`;\r\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.
|
|
3
|
+
"version": "1.1.1",
|
|
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",
|
|
@@ -43,5 +43,14 @@
|
|
|
43
43
|
"bugs": {
|
|
44
44
|
"url": "https://github.com/fomadev/next-advanced-sitemap/issues"
|
|
45
45
|
},
|
|
46
|
-
"homepage": "https://github.com/fomadev/next-advanced-sitemap#readme"
|
|
46
|
+
"homepage": "https://github.com/fomadev/next-advanced-sitemap#readme",
|
|
47
|
+
"devDependencies": {
|
|
48
|
+
"tsup": "^8.5.1",
|
|
49
|
+
"typescript": "^5.7.3",
|
|
50
|
+
"vitest": "^1.4.0"
|
|
51
|
+
},
|
|
52
|
+
"peerDependencies": {
|
|
53
|
+
"next": ">=13.0.0",
|
|
54
|
+
"react": ">=18.0.0"
|
|
55
|
+
}
|
|
47
56
|
}
|