@nuxtjs/sitemap 8.3.4 → 8.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,7 +1,10 @@
1
+ import { fileURLToPath } from 'node:url'
1
2
  import { resolve } from 'pathe'
2
3
 
4
+ const currentDir = fileURLToPath(new URL('.', import.meta.url))
5
+
3
6
  // Nuxt SEO devtools panel, shipped as a layer (Model C). Components flat-registered
4
7
  // so intra-panel references resolve by name.
5
8
  export default defineNuxtConfig({
6
- components: [{ path: resolve(__dirname, './components'), pathPrefix: false }],
9
+ components: [{ path: resolve(currentDir, './components'), pathPrefix: false }],
7
10
  })
package/dist/module.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "nuxt": ">=3.9.0"
5
5
  },
6
6
  "configKey": "sitemap",
7
- "version": "8.3.4",
7
+ "version": "8.5.0",
8
8
  "builder": {
9
9
  "@nuxt/module-builder": "1.0.3",
10
10
  "unbuild": "3.6.1"
package/dist/module.mjs CHANGED
@@ -2,15 +2,18 @@ import { useNuxt, hasNuxtModule, addTypeTemplate, addTemplate, defineNuxtModule,
2
2
  import { defu } from 'defu';
3
3
  import { withSiteUrl, installNuxtSiteConfig } from 'nuxt-site-config/kit';
4
4
  import { isPathFile } from 'nuxt-site-config/urls';
5
- import { isNuxtGenerate, renderNitroTypeAugmentations, useModuleLogger, setupNitroRuntimeCompatibility, getNuxtModuleOptions, resolveNitroPreset, resolveNuxtContentVersion, createPagesPromise, createNitroPromise } from 'nuxtseo-shared/kit';
5
+ import { isNuxtGenerate, renderNitroTypeAugmentations, useModuleLogger, setupNitroRuntimeCompatibility, getNuxtModuleOptions, resolveNitroPreset, resolveContentProvider, setupContentRuntime, createPagesPromise, createNitroPromise } from 'nuxtseo-shared/kit';
6
6
  import { serializeFilters } from 'nuxtseo-shared/utils';
7
7
  import { dirname, extname } from 'pathe';
8
8
  import { readPackageJSON } from 'pkg-types';
9
9
  import { withBase, withHttps, withTrailingSlash, withoutLeadingSlash, joinURL, withLeadingSlash } from 'ufo';
10
10
  import { setupDevToolsUI as setupDevToolsUI$1 } from 'nuxtseo-shared/devtools';
11
+ import { once } from 'node:events';
11
12
  import { readFileSync, statSync } from 'node:fs';
12
- import { mkdir, writeFile } from 'node:fs/promises';
13
- import { join } from 'node:path';
13
+ import { rm, mkdir, writeFile } from 'node:fs/promises';
14
+ import { join, resolve } from 'node:path';
15
+ import { pathToFileURL } from 'node:url';
16
+ import { Worker, MessageChannel } from 'node:worker_threads';
14
17
  import { colors } from 'consola/utils';
15
18
  import { splitForLocales, createPathFilter } from '../dist/runtime/utils-pure.js';
16
19
  import { p as parseHtmlExtractSitemapMeta } from './shared/sitemap.BoMnWHOt.mjs';
@@ -18,6 +21,9 @@ import { normaliseDate } from '../dist/runtime/server/sitemap/urlset/normalise.j
18
21
  import { mapPathForI18nPages, splitPathForI18nLocales as splitPathForI18nLocales$1, expandCompactLocaleRoute, normalizeLocales, generatePathForI18nPages } from 'nuxtseo-shared/i18n';
19
22
  import 'ultrahtml';
20
23
 
24
+ const COMARK_CONTENT_SOURCE = "@harlan-zw/comark-content:urls";
25
+ const COMARK_CONTENT_SITEMAP_ROUTE = "/__sitemap__/comark-content-urls.json";
26
+
21
27
  function setupDevToolsUI(_options, resolve, nuxt = useNuxt()) {
22
28
  setupDevToolsUI$1(
23
29
  { route: "/__nuxt-sitemap", name: "sitemap", title: "Sitemap", icon: "carbon:load-balancer-application" },
@@ -72,6 +78,14 @@ export async function readSourcesFromFilesystem(filename) {
72
78
  `;
73
79
  });
74
80
  nuxt.hooks.hook("nitro:init", async (nitro) => {
81
+ let prerendererNitro = nitro;
82
+ nitro.hooks.hook("prerender:init", (prerenderer) => {
83
+ prerendererNitro = prerenderer;
84
+ });
85
+ await Promise.all([
86
+ rm(join(runtimeAssetsPath, "global-sources.json"), { force: true }),
87
+ rm(join(runtimeAssetsPath, "child-sources.json"), { force: true })
88
+ ]);
75
89
  nitro.hooks.hook("prerender:generate", async (route) => {
76
90
  const html = route.contents;
77
91
  if (!route.fileName?.endsWith(".html") || !html || ["/200.html", "/404.html"].includes(route.route))
@@ -121,12 +135,101 @@ export async function readSourcesFromFilesystem(filename) {
121
135
  await writeFile(join(runtimeAssetsPath, "global-sources.json"), JSON.stringify(globalSources));
122
136
  await writeFile(join(runtimeAssetsPath, "child-sources.json"), JSON.stringify(childSources));
123
137
  const sitemapEntry = options.isMultiSitemap ? "/sitemap_index.xml" : `/${Object.keys(options.sitemaps)[0]}`;
124
- const sitemaps = await prerenderSitemapsFromEntry(nitro, sitemapEntry);
125
- await nuxt.hooks.callHook("sitemap:prerender:done", { options, sitemaps });
138
+ const prerenderServer = await loadPrerenderServer(prerendererNitro);
139
+ await prerenderSitemapsFromEntry(nitro, prerenderServer.fetch, sitemapEntry).then((sitemaps) => nuxt.hooks.callHook("sitemap:prerender:done", { options, sitemaps })).finally(prerenderServer.close);
126
140
  });
127
141
  });
128
142
  }
129
- async function prerenderSitemapsFromEntry(nitro, entry) {
143
+ const PrerenderWorkerCode = `
144
+ const { parentPort, workerData } = require('node:worker_threads')
145
+
146
+ ;(async () => {
147
+ const serverEntry = await import(workerData.entry)
148
+ const server = serverEntry.default
149
+ const localFetch = serverEntry.localFetch
150
+ const fetch = typeof server?.fetch === 'function'
151
+ ? (input, headers) => server.fetch(new Request(new URL(input, 'http://localhost'), { headers }))
152
+ : typeof localFetch === 'function'
153
+ // older nitropack exposes an ofetch instance: a bare call returns parsed data, .raw returns the response
154
+ ? (input, headers) => (localFetch.raw ?? localFetch)(input, { headers })
155
+ : undefined
156
+ const close = typeof server?.close === 'function'
157
+ ? () => server.close()
158
+ : typeof serverEntry.closePrerenderer === 'function'
159
+ ? () => serverEntry.closePrerenderer()
160
+ : async () => {}
161
+
162
+ parentPort.postMessage(fetch ? { _tag: 'Ready' } : { _tag: 'Err', message: 'Nitro prerender server does not expose a fetch handler' })
163
+ parentPort.on('message', ({ _tag, input, headers, port }) => {
164
+ const task = _tag === 'Fetch'
165
+ ? fetch(input, headers).then(async response => ({
166
+ _tag: 'Ok',
167
+ status: response.status,
168
+ statusText: response.statusText,
169
+ headers: [...response.headers],
170
+ body: await response.arrayBuffer(),
171
+ }))
172
+ : close().then(() => ({ _tag: 'Ok' }))
173
+ task.catch(error => ({ _tag: 'Err', message: error instanceof Error ? error.message : String(error) }))
174
+ .then(result => port.postMessage(result, result.body ? [result.body] : []))
175
+ })
176
+ })().catch(error => parentPort.postMessage({ _tag: 'Err', message: error instanceof Error ? error.message : String(error) }))
177
+ `;
178
+ function raceWithTimeout(promises, ms, message) {
179
+ let timer;
180
+ const timeout = new Promise((_, reject) => {
181
+ timer = setTimeout(() => reject(new Error(message)), ms);
182
+ });
183
+ return Promise.race([...promises, timeout]).finally(() => clearTimeout(timer));
184
+ }
185
+ async function loadPrerenderServer(nitro) {
186
+ const entryFileNames = nitro.options.rollupConfig?.output?.entryFileNames;
187
+ const serverFilename = typeof entryFileNames === "string" ? entryFileNames : "index.mjs";
188
+ const worker = new Worker(PrerenderWorkerCode, {
189
+ eval: true,
190
+ workerData: { entry: pathToFileURL(resolve(nitro.options.output.serverDir, serverFilename)).href }
191
+ });
192
+ const workerDead = new Promise((_, reject) => {
193
+ worker.once("exit", (code) => reject(new Error(`The Nitro prerender server worker exited unexpectedly (code ${code})`)));
194
+ worker.once("error", (error) => reject(error));
195
+ });
196
+ workerDead.catch(() => {
197
+ });
198
+ try {
199
+ const [ready] = await raceWithTimeout([once(worker, "message"), workerDead], 6e4, "The Nitro prerender server did not become ready in time");
200
+ if (ready._tag === "Err")
201
+ throw new Error(ready.message);
202
+ } catch (error) {
203
+ await worker.terminate();
204
+ throw error;
205
+ }
206
+ const callWorker = async (message) => {
207
+ const { port1, port2 } = new MessageChannel();
208
+ worker.postMessage({ ...message, port: port2 }, [port2]);
209
+ try {
210
+ const [result] = await Promise.race([once(port1, "message"), workerDead]);
211
+ if (result._tag === "Err")
212
+ throw new Error(result.message);
213
+ return result;
214
+ } finally {
215
+ port1.close();
216
+ }
217
+ };
218
+ return {
219
+ async fetch(input, headers) {
220
+ const { _tag, body, ...responseInit } = await callWorker({ _tag: "Fetch", input, headers });
221
+ return new Response(body, responseInit);
222
+ },
223
+ async close() {
224
+ try {
225
+ await raceWithTimeout([callWorker({ _tag: "Close" })], 3e3, "The Nitro prerender server did not close in time");
226
+ } catch {
227
+ }
228
+ await worker.terminate();
229
+ }
230
+ };
231
+ }
232
+ async function prerenderSitemapsFromEntry(nitro, fetch, entry) {
130
233
  const sitemaps = [];
131
234
  const queue = [entry];
132
235
  const processed = /* @__PURE__ */ new Set();
@@ -135,7 +238,7 @@ async function prerenderSitemapsFromEntry(nitro, entry) {
135
238
  if (processed.has(route))
136
239
  continue;
137
240
  processed.add(route);
138
- const { filePath, prerenderUrls } = await prerenderRoute(nitro, route);
241
+ const { filePath, prerenderUrls } = await prerenderRoute(nitro, fetch, route);
139
242
  sitemaps.push({
140
243
  name: route,
141
244
  get content() {
@@ -146,27 +249,19 @@ async function prerenderSitemapsFromEntry(nitro, entry) {
146
249
  }
147
250
  return sitemaps;
148
251
  }
149
- async function prerenderRoute(nitro, route) {
252
+ async function prerenderRoute(nitro, fetch, route) {
150
253
  const start = Date.now();
151
254
  const _route = { route, fileName: route };
152
255
  const encodedRoute = encodeURI(route);
153
256
  const fetchUrl = withBase(encodedRoute, nitro.options.baseURL);
154
- const res = await globalThis.$fetch.raw(
155
- fetchUrl,
156
- {
157
- headers: { "x-nitro-prerender": encodedRoute },
158
- retry: nitro.options.prerender.retry,
159
- retryDelay: nitro.options.prerender.retryDelay
160
- }
161
- );
257
+ const res = await fetch(fetchUrl, { "x-nitro-prerender": encodedRoute });
258
+ if (!res.ok)
259
+ throw new Error(`Failed to prerender '${fetchUrl}': ${res.status} ${res.statusText}`);
162
260
  const header = res.headers.get("x-nitro-prerender") || "";
163
261
  const prerenderUrls = header.split(",").map((i) => decodeURIComponent(i.trim())).filter(Boolean);
164
262
  const filePath = join(nitro.options.output.publicDir, _route.fileName);
165
263
  await mkdir(dirname(filePath), { recursive: true });
166
- const data = res._data;
167
- if (data === void 0)
168
- throw new Error(`No data returned from '${fetchUrl}'`);
169
- const content = filePath.endsWith("json") || typeof data === "object" ? JSON.stringify(data) : data;
264
+ const content = await res.text();
170
265
  await writeFile(filePath, content, "utf8");
171
266
  _route.generateTimeMS = Date.now() - start;
172
267
  nitro._prerenderedRoutes.push(_route);
@@ -185,7 +280,8 @@ function registerTypeTemplates(nitroCompatibility) {
185
280
  'sitemap:input': (ctx: SitemapInputCtx<${nitroCompatibility.eventType}>) => void | Promise<void>
186
281
  'sitemap:resolved': (ctx: SitemapRenderCtx<${nitroCompatibility.eventType}>) => void | Promise<void>
187
282
  'sitemap:output': (ctx: SitemapOutputHookCtx<${nitroCompatibility.eventType}>) => void | Promise<void>
188
- 'sitemap:sources': (ctx: SitemapSourcesHookCtx<${nitroCompatibility.eventType}>) => void | Promise<void>`;
283
+ 'sitemap:sources': (ctx: SitemapSourcesHookCtx<${nitroCompatibility.eventType}>) => void | Promise<void>
284
+ 'sitemap:sitemaps-resolved': (ctx: SitemapsResolvedCtx<${nitroCompatibility.eventType}>) => void | Promise<void>`;
189
285
  const nitroTypes = renderNitroTypeAugmentations(nitroCompatibility, {
190
286
  nitroInterfaces: {
191
287
  PrerenderRoute: "_sitemap?: SitemapUrl"
@@ -196,7 +292,7 @@ function registerTypeTemplates(nitroCompatibility) {
196
292
  });
197
293
  return `// Generated by @nuxtjs/sitemap
198
294
  /// <reference path="./nuxt-sitemap-virtual.d.ts" />
199
- import type { SitemapUrl, SitemapItemDefaults, SitemapIndexRenderCtx, SitemapInputCtx, SitemapRenderCtx, SitemapOutputHookCtx, SitemapSourcesHookCtx } from '@nuxtjs/sitemap'
295
+ import type { SitemapUrl, SitemapItemDefaults, SitemapIndexRenderCtx, SitemapInputCtx, SitemapRenderCtx, SitemapOutputHookCtx, SitemapSourcesHookCtx, SitemapsResolvedCtx } from '@nuxtjs/sitemap'
200
296
 
201
297
  ${nitroTypes}
202
298
 
@@ -455,7 +551,34 @@ function generateExtraRoutesFromNuxtConfig(nuxt = useNuxt()) {
455
551
  }).map(([k]) => k).filter(filterForValidPage);
456
552
  return { routeRules };
457
553
  }
554
+ function resolveExcludedAppSources(resolved, authored) {
555
+ if (resolved === true || authored === true)
556
+ return true;
557
+ if (!Array.isArray(authored))
558
+ return resolved;
559
+ const excluded = [...resolved];
560
+ for (const source of authored) {
561
+ if (typeof source === "string" && !excluded.includes(source))
562
+ excluded.push(source);
563
+ }
564
+ return excluded;
565
+ }
458
566
 
567
+ const IMAGE_TAGS = /* @__PURE__ */ new Set(["image", "img", "nuxtimg", "nuxt-img"]);
568
+ function discoverContentImages(body) {
569
+ const images = [];
570
+ const walk = (nodes) => {
571
+ for (const node of nodes || []) {
572
+ if (!Array.isArray(node) || typeof node[0] !== "string")
573
+ continue;
574
+ if (IMAGE_TAGS.has(node[0]) && node[1]?.src)
575
+ images.push({ loc: node[1].src });
576
+ walk(node.slice(2));
577
+ }
578
+ };
579
+ walk(body?.value ?? body?.nodes);
580
+ return images;
581
+ }
459
582
  const module$1 = defineNuxtModule({
460
583
  meta: {
461
584
  name: "@nuxtjs/sitemap",
@@ -480,6 +603,10 @@ const module$1 = defineNuxtModule({
480
603
  version: ">=2",
481
604
  optional: true
482
605
  },
606
+ "@harlan-zw/comark-content": {
607
+ version: ">=0.1.2",
608
+ optional: true
609
+ },
483
610
  "@nuxtjs/robots": {
484
611
  version: ">=4",
485
612
  optional: true
@@ -673,11 +800,11 @@ const module$1 = defineNuxtModule({
673
800
  const hasCustomI18nSitemaps = i18nSitemaps.length > 0;
674
801
  if (hasCustomI18nSitemaps) {
675
802
  for (const { name: name2, cfg } of i18nSitemaps) {
803
+ const { sitemapName: _sitemapName, _route, _isChunking, _chunkSize, _chunkCount, ...inheritedConfig } = cfg;
676
804
  for (const locale of resolvedAutoI18n.locales) {
677
805
  newSitemaps[`${locale._sitemap}-${name2}`] = {
678
- includeAppSources: true,
679
- ...cfg.exclude?.length && { exclude: cfg.exclude },
680
- ...cfg.include?.length && { include: cfg.include }
806
+ ...inheritedConfig,
807
+ includeAppSources: true
681
808
  };
682
809
  }
683
810
  }
@@ -770,20 +897,13 @@ const module$1 = defineNuxtModule({
770
897
  addServerPlugin(resolve("./runtime/server/plugins/stream-transport"));
771
898
  }
772
899
  const isNuxtContentDocumentDriven = !!nuxt.options.content?.documentDriven || config.strictNuxtContentPaths;
773
- const contentVersion = await resolveNuxtContentVersion();
774
- const isNuxtContentV3 = contentVersion && contentVersion.version === 3;
900
+ const contentProvider = await resolveContentProvider(nuxt);
901
+ const isNuxtContentV3 = contentProvider._tag === "NuxtContent" && contentProvider.version === 3;
902
+ const isNuxtContentV2 = contentProvider._tag === "NuxtContent" && contentProvider.version === 2;
903
+ const isComarkContent = contentProvider._tag === "Comark";
775
904
  const nuxtV3Collections = /* @__PURE__ */ new Set();
776
- const isNuxtContentV2 = contentVersion && contentVersion.version === 2;
777
- if (isNuxtContentV3) {
778
- if (nuxt.options._installedModules.some((m) => m.meta.name === "Content")) {
779
- logger.warn("You have loaded `@nuxt/content` before `@nuxtjs/sitemap`, this may cause issues with the integration. Please ensure `@nuxtjs/sitemap` is loaded first.");
780
- }
781
- config.exclude.push("/__nuxt_content/**");
782
- const needsCustomAlias = await hasNuxtModuleCompatibility("@nuxt/content", "<3.6.0");
783
- if (needsCustomAlias) {
784
- nuxt.options.alias["#sitemap/content-v3-nitro-path"] = resolve(dirname(resolveModule("@nuxt/content")), "runtime/nitro");
785
- nuxt.options.alias["@nuxt/content/nitro"] = resolve("./runtime/server/content-compat");
786
- }
905
+ setupContentRuntime(contentProvider, nuxt);
906
+ const registerContentSitemapHook = (options) => {
787
907
  nuxt.hooks.hook("content:file:afterParse", (ctx) => {
788
908
  try {
789
909
  const content = ctx.content;
@@ -792,7 +912,7 @@ const module$1 = defineNuxtModule({
792
912
  ctx.content.sitemap = null;
793
913
  return;
794
914
  }
795
- if (!ctx.collection.fields || !("sitemap" in ctx.collection.fields)) {
915
+ if (options.requireCollectionField && (!ctx.collection.fields || !("sitemap" in ctx.collection.fields))) {
796
916
  ctx.content.sitemap = null;
797
917
  return;
798
918
  }
@@ -805,13 +925,8 @@ const module$1 = defineNuxtModule({
805
925
  return;
806
926
  }
807
927
  const images = [];
808
- if (config.discoverImages) {
809
- images.push(
810
- ...content.body?.value?.filter(
811
- (c) => ["image", "img", "nuxtimg", "nuxt-img"].includes(c[0])
812
- ).filter((c) => c[1]?.src).map((c) => ({ loc: c[1].src })) || []
813
- );
814
- }
928
+ if (config.discoverImages)
929
+ images.push(...discoverContentImages(content.body));
815
930
  const lastmod = content.seo?.articleModifiedTime || content.updatedAt;
816
931
  const defaults = {
817
932
  loc: content.path
@@ -825,6 +940,8 @@ const module$1 = defineNuxtModule({
825
940
  logger.warn(`Failed to process sitemap data for content file (collection: ${ctx.collection?.name}, path: ${ctx.content?.path}), skipping.`, e);
826
941
  }
827
942
  });
943
+ };
944
+ const addContentCallbackVirtuals = () => {
828
945
  nuxt.hook("nitro:config", (nitroConfig) => {
829
946
  const filterEntries = [];
830
947
  if (globalThis.__sitemapCollectionFilters) {
@@ -842,9 +959,22 @@ ${filterEntries.join("\n")}`;
842
959
  nitroConfig.virtual["#sitemap/content-on-url"] = `export const onUrlFns = new Map()
843
960
  ${onUrlEntries.join("\n")}`;
844
961
  });
962
+ };
963
+ if (isNuxtContentV3) {
964
+ if (nuxt.options._installedModules.some((m) => m.meta.name === "Content")) {
965
+ logger.warn("You have loaded `@nuxt/content` before `@nuxtjs/sitemap`, this may cause issues with the integration. Please ensure `@nuxtjs/sitemap` is loaded first.");
966
+ }
967
+ config.exclude.push("/__nuxt_content/**");
968
+ const needsCustomAlias = await hasNuxtModuleCompatibility("@nuxt/content", "<3.6.0");
969
+ if (needsCustomAlias) {
970
+ nuxt.options.alias["#sitemap/content-v3-nitro-path"] = resolve(dirname(resolveModule("@nuxt/content")), "runtime/nitro");
971
+ nuxt.options.alias["@nuxt/content/nitro"] = resolve("./runtime/server/content-compat");
972
+ }
973
+ registerContentSitemapHook({ requireCollectionField: true });
974
+ addContentCallbackVirtuals();
845
975
  addServerHandler({
846
976
  route: "/__sitemap__/nuxt-content-urls.json",
847
- handler: resolve("./runtime/server/routes/__sitemap__/nuxt-content-urls-v3")
977
+ handler: resolve("./runtime/server/routes/__sitemap__/content-urls")
848
978
  });
849
979
  if (config.strictNuxtContentPaths) {
850
980
  logger.warn("You have set `strictNuxtContentPaths: true` but are using @nuxt/content v3. This is not required, please remove it.");
@@ -857,6 +987,24 @@ ${onUrlEntries.join("\n")}`;
857
987
  },
858
988
  fetch: "/__sitemap__/nuxt-content-urls.json"
859
989
  });
990
+ } else if (isComarkContent) {
991
+ registerContentSitemapHook({ requireCollectionField: false });
992
+ addContentCallbackVirtuals();
993
+ addServerHandler({
994
+ route: COMARK_CONTENT_SITEMAP_ROUTE,
995
+ handler: resolve("./runtime/server/routes/__sitemap__/content-urls")
996
+ });
997
+ if (config.strictNuxtContentPaths) {
998
+ logger.warn("You have set `strictNuxtContentPaths: true` but are using comark-content. This is not required, please remove it.");
999
+ }
1000
+ appGlobalSources.push({
1001
+ context: {
1002
+ name: COMARK_CONTENT_SOURCE,
1003
+ description: "Generated from your markdown files.",
1004
+ tips: nuxtV3Collections.size ? [`Parsing the following collections: ${Array.from(nuxtV3Collections).join(", ")}`] : ["No collections found. Set `sitemap: false` on a collection to keep it out."]
1005
+ },
1006
+ fetch: COMARK_CONTENT_SITEMAP_ROUTE
1007
+ });
860
1008
  } else if (isNuxtContentV2) {
861
1009
  addServerPlugin(resolve("./runtime/server/plugins/nuxt-content-v2"));
862
1010
  addServerHandler({
@@ -1097,6 +1245,10 @@ ${onUrlEntries.join("\n")}`;
1097
1245
  return r.contentType?.includes("text/html");
1098
1246
  };
1099
1247
  const generateGlobalSources = async () => {
1248
+ const excludedAppSources = resolveExcludedAppSources(
1249
+ config.excludeAppSources,
1250
+ nuxt.options.sitemap?.excludeAppSources
1251
+ );
1100
1252
  const { routeRules: routeRules2 } = generateExtraRoutesFromNuxtConfig();
1101
1253
  const nitro = await nitroPromise;
1102
1254
  const prerenderedRoutes2 = nitro._prerenderedRoutes || [];
@@ -1173,7 +1325,7 @@ ${onUrlEntries.join("\n")}`;
1173
1325
  s.sourceType = "user";
1174
1326
  return s;
1175
1327
  }),
1176
- ...(config.excludeAppSources === true ? [] : [
1328
+ ...(excludedAppSources === true ? [] : [
1177
1329
  ...appGlobalSources,
1178
1330
  {
1179
1331
  context: {
@@ -1205,7 +1357,7 @@ ${onUrlEntries.join("\n")}`;
1205
1357
  },
1206
1358
  urls: prerenderUrlsFinal
1207
1359
  }
1208
- ]).filter((s) => !config.excludeAppSources.includes(s.context.name) && (!!s.urls?.length || !!s.fetch)).map((s) => {
1360
+ ]).filter((s) => !excludedAppSources.includes(s.context.name) && (!!s.urls?.length || !!s.fetch)).map((s) => {
1209
1361
  s.sourceType = "app";
1210
1362
  return s;
1211
1363
  })
@@ -0,0 +1,9 @@
1
+ /**
2
+ * URLs from the installed content module's page collections.
3
+ *
4
+ * One route for every provider. `#nuxtseo/content` normalizes the manifest shape
5
+ * and the query builder; the `sitemap` field on each entry is written by the
6
+ * module's `content:file:afterParse` hook.
7
+ */
8
+ declare const _default: any;
9
+ export default _default;
@@ -0,0 +1,34 @@
1
+ import * as contentRuntime from "#nuxtseo/content";
2
+ import { defineEventHandler } from "#nuxtseo/h3";
3
+ import { filters } from "#sitemap/content-filters";
4
+ import { onUrlFns } from "#sitemap/content-on-url";
5
+ const { listPageCollections, provider, queryPages } = contentRuntime;
6
+ export default defineEventHandler(async (e) => {
7
+ const collections = (await listPageCollections(e)).filter((collection) => collection.inSitemap && collection.hasField("sitemap")).map((collection) => collection.name);
8
+ const results = await Promise.all(collections.map(async (collection) => {
9
+ const needsAllFields = filters?.has(collection) || onUrlFns?.has(collection);
10
+ const query = queryPages(e, collection).where("path", "IS NOT NULL").where("sitemap", "IS NOT NULL");
11
+ if (!needsAllFields)
12
+ query.select("path", "sitemap");
13
+ try {
14
+ const entries = await query.all();
15
+ const filter = filters?.get(collection);
16
+ return { collection, entries: filter ? entries.filter(filter) : entries };
17
+ } catch (err) {
18
+ const hint = provider === "nuxt-content-v3" ? " On serverless the content DB is restored from a prerendered sql_dump.txt that isn't readable inside the function (nuxt/content#3805). Fix: prerender the sitemap so content URLs resolve at build, or configure a runtime database (D1/Turso/Postgres)." : "";
19
+ console.error(`[@nuxtjs/sitemap] Couldn't query content collection "${collection}" for the sitemap, so its URLs will be missing.${hint}`, err);
20
+ return { collection, entries: [] };
21
+ }
22
+ }));
23
+ return results.flatMap(({ collection, entries }) => {
24
+ const onUrl = onUrlFns?.get(collection);
25
+ return entries.filter((entry) => entry.sitemap !== false && entry.path && !entry.path.endsWith(".navigation")).map((entry) => {
26
+ const url = {
27
+ loc: entry.path,
28
+ ...typeof entry.sitemap === "object" && entry.sitemap ? entry.sitemap : {}
29
+ };
30
+ onUrl?.(url, entry, collection);
31
+ return url;
32
+ });
33
+ });
34
+ });
@@ -6,7 +6,7 @@ import {
6
6
  globalSitemapSources,
7
7
  resolveSitemapSources
8
8
  } from "../../sitemap/urlset/sources.js";
9
- import { useSitemapRuntimeConfig } from "../../utils.js";
9
+ import { useResolvedSitemapRuntimeConfig } from "../../utils.js";
10
10
  function attachUrlWarnings(sources) {
11
11
  for (const source of sources) {
12
12
  if (!source.urls?.length)
@@ -26,7 +26,7 @@ function attachUrlWarnings(sources) {
26
26
  return sources;
27
27
  }
28
28
  export default defineEventHandler(async (e) => {
29
- const _runtimeConfig = useSitemapRuntimeConfig();
29
+ const _runtimeConfig = await useResolvedSitemapRuntimeConfig(e);
30
30
  const siteConfig = getSiteConfig(e);
31
31
  const { sitemaps: _sitemaps } = _runtimeConfig;
32
32
  const runtimeConfig = { ..._runtimeConfig };
@@ -3,6 +3,7 @@ import { getHeader } from "#nuxtseo/h3";
3
3
  import { defineCachedFunction } from "#nuxtseo/nitro";
4
4
  import staticConfig from "#sitemap-virtual/static-config.mjs";
5
5
  import { normaliseDate } from "../urlset/normalise.js";
6
+ import { parseChunkInfo } from "../utils/chunk.js";
6
7
  import { getResolvedSitemapUrls } from "./sitemap.js";
7
8
  const SERVER_CACHE_MAX_AGE = staticConfig.cacheMaxAgeSeconds || 60 * 10;
8
9
  const buildSitemapIndexCached = defineCachedFunction(
@@ -43,7 +44,9 @@ async function buildSitemapIndexInternal(resolvers, runtimeConfig, nitro) {
43
44
  sitemapConfig._isChunking = true;
44
45
  sitemapConfig._chunkSize = sitemapConfig.chunkSize || (typeof sitemapConfig.chunks === "number" ? sitemapConfig.chunks : defaultSitemapsChunkSize || 1e3);
45
46
  } else {
46
- nonChunkedNames.push(sitemapName);
47
+ const chunkInfo = parseChunkInfo(sitemapName, sitemaps, defaultSitemapsChunkSize || void 0);
48
+ if (!chunkInfo.isChunked)
49
+ nonChunkedNames.push(sitemapName);
47
50
  }
48
51
  }
49
52
  const indexLastmod = autoLastmod ? normaliseDate(/* @__PURE__ */ new Date()) : void 0;
@@ -50,7 +50,7 @@ export async function buildResolvedSitemapUrls(effectiveSitemap, matchName, isCh
50
50
  if (typeof e._sitemap === "string" && !hasMatchingSitemap) {
51
51
  if (!warnedSitemaps.has(e._sitemap)) {
52
52
  warnedSitemaps.add(e._sitemap);
53
- logger.error(`Sitemap \`${e._sitemap}\` not found in sitemap config. Available sitemaps: ${sitemapNames.join(", ")}. Entry \`${e.loc}\` will be omitted.`);
53
+ logger.error(`Sitemap \`${e._sitemap}\` not found in sitemap config. Available sitemaps: ${sitemapNames.join(", ")}. Either add it to the sitemap config or register it with the sitemap:sitemaps-resolved hook. Entry \`${e.loc}\` will be omitted.`);
54
54
  }
55
55
  }
56
56
  }
@@ -88,7 +88,13 @@ export const buildResolvedSitemapUrlsCached = defineCachedFunction(
88
88
  const proto = getHeader(event, "x-forwarded-proto") || "https";
89
89
  return `resolved-${isChunked ? "chunked-" : ""}${matchName}-${proto}-${host}`;
90
90
  },
91
- swr: true
91
+ swr: true,
92
+ // A build with failed sources is never cached: one outage must not pin an empty sitemap
93
+ // for a whole cache window. The next request retries the sources instead.
94
+ validate: (entry) => {
95
+ const value = entry.value;
96
+ return value !== void 0 && !value.failedSources?.length;
97
+ }
92
98
  }
93
99
  );
94
100
  export async function getResolvedSitemapUrls(effectiveSitemap, matchName, isChunked, resolvers, runtimeConfig, nitro) {
@@ -1,20 +1,20 @@
1
1
  import { joinURL, withBase, withLeadingSlash, withoutLeadingSlash, withoutTrailingSlash } from "ufo";
2
2
  import { appendHeader, createError, getRequestURL, getRouterParam, sendRedirect } from "#nuxtseo/h3";
3
3
  import { useNitroApp, useRuntimeConfig } from "#nuxtseo/nitro";
4
- import { useSitemapRuntimeConfig } from "../utils.js";
4
+ import { useResolvedSitemapRuntimeConfig } from "../utils.js";
5
5
  import { urlsToIndexXml, urlsToIndexXmlStream } from "./builder/index-xml.js";
6
6
  import { buildSitemapIndex } from "./builder/sitemap-index.js";
7
7
  import { createSitemap, renderSitemapOutput, setSitemapResponseHeaders, useNitroUrlResolvers } from "./nitro.js";
8
8
  import { getSitemapConfig, parseChunkInfo } from "./utils/chunk.js";
9
9
  export async function sitemapXmlEventHandler(e) {
10
- const runtimeConfig = useSitemapRuntimeConfig();
10
+ const runtimeConfig = await useResolvedSitemapRuntimeConfig(e);
11
11
  const { sitemaps } = runtimeConfig;
12
12
  if ("index" in sitemaps)
13
13
  return sendRedirect(e, withBase("/sitemap_index.xml", useRuntimeConfig().app.baseURL), import.meta.dev ? 302 : 301);
14
14
  return createSitemap(e, Object.values(sitemaps)[0], runtimeConfig);
15
15
  }
16
16
  export async function sitemapIndexXmlEventHandler(e) {
17
- const runtimeConfig = useSitemapRuntimeConfig();
17
+ const runtimeConfig = await useResolvedSitemapRuntimeConfig(e);
18
18
  const nitro = useNitroApp();
19
19
  const resolvers = useNitroUrlResolvers(e);
20
20
  const { entries: sitemaps, failedSources } = await buildSitemapIndex(resolvers, runtimeConfig, nitro);
@@ -44,7 +44,7 @@ export async function sitemapChildXmlEventHandler(e) {
44
44
  const pathname = getRequestURL(e).pathname;
45
45
  if (!pathname.endsWith(".xml"))
46
46
  return;
47
- const runtimeConfig = useSitemapRuntimeConfig(e);
47
+ const runtimeConfig = await useResolvedSitemapRuntimeConfig(e);
48
48
  const { sitemaps } = runtimeConfig;
49
49
  let sitemapName = getRouterParam(e, "sitemap");
50
50
  if (!sitemapName) {
@@ -66,7 +66,7 @@ export async function sitemapChildXmlEventHandler(e) {
66
66
  sitemapName = withoutLeadingSlash(withoutTrailingSlash(sitemapName));
67
67
  const chunkInfo = parseChunkInfo(sitemapName, sitemaps, runtimeConfig.defaultSitemapsChunkSize);
68
68
  const isAutoChunked = typeof sitemaps.chunks !== "undefined" && !Number.isNaN(Number(sitemapName));
69
- const sitemapExists = sitemapName in sitemaps || chunkInfo.baseSitemapName in sitemaps || isAutoChunked;
69
+ const sitemapExists = Object.hasOwn(sitemaps, sitemapName) || Object.hasOwn(sitemaps, chunkInfo.baseSitemapName) || isAutoChunked;
70
70
  if (!sitemapExists)
71
71
  throw createError({ statusCode: 404, message: `Sitemap "${sitemapName}" not found.` });
72
72
  if (chunkInfo.isChunked && chunkInfo.chunkIndex !== void 0) {
@@ -2,8 +2,9 @@ import { defu } from "defu";
2
2
  import { $fetch } from "ofetch";
3
3
  import { collectSitemap } from "sitemapd/parse";
4
4
  import { parseURL } from "ufo";
5
- import { getRequestHost } from "#nuxtseo/h3";
6
- import { fetchWithEvent } from "#nuxtseo/nitro";
5
+ import { getHeader, getRequestHost } from "#nuxtseo/h3";
6
+ import { defineCachedFunction, fetchWithEvent } from "#nuxtseo/nitro";
7
+ import staticConfig from "#sitemap-virtual/static-config.mjs";
7
8
  import { logger } from "../../../utils-pure.js";
8
9
  const changeFrequencies = /* @__PURE__ */ new Set([
9
10
  "always",
@@ -69,10 +70,78 @@ async function tryFetchWithFallback(url, options, event) {
69
70
  }
70
71
  return event ? await fetchWithEvent(event, url, options) : await globalThis.$fetch(url, options);
71
72
  }
73
+ const SOURCE_FETCH_MEMO_KEY = "_sitemapSourceFetches";
74
+ const SERVER_CACHE_MAX_AGE = staticConfig.cacheMaxAgeSeconds || 60 * 10;
75
+ const prerenderSourceFetches = import.meta.prerender ? /* @__PURE__ */ new Map() : void 0;
76
+ function useSourceFetchMemo(event) {
77
+ if (import.meta.prerender)
78
+ return prerenderSourceFetches;
79
+ const context = event?.context;
80
+ if (!context)
81
+ return void 0;
82
+ const existing = context[SOURCE_FETCH_MEMO_KEY];
83
+ if (existing)
84
+ return existing;
85
+ const memo = /* @__PURE__ */ new Map();
86
+ context[SOURCE_FETCH_MEMO_KEY] = memo;
87
+ return memo;
88
+ }
89
+ function hashCacheKey(key) {
90
+ let h1 = 3735928559;
91
+ let h2 = 1103547991;
92
+ for (let i = 0; i < key.length; i++) {
93
+ const ch = key.charCodeAt(i);
94
+ h1 = Math.imul(h1 ^ ch, 2654435761);
95
+ h2 = Math.imul(h2 ^ ch, 1597334677);
96
+ }
97
+ h1 = Math.imul(h1 ^ h1 >>> 16, 2246822507) ^ Math.imul(h2 ^ h2 >>> 13, 3266489909);
98
+ h2 = Math.imul(h2 ^ h2 >>> 16, 2246822507) ^ Math.imul(h1 ^ h1 >>> 13, 3266489909);
99
+ return (4294967296 * (2097151 & h2) + (h1 >>> 0)).toString(36);
100
+ }
101
+ const fetchSourceUrlsCached = defineCachedFunction(
102
+ (event, _key, url, options) => fetchSourceUrls(url, options, event),
103
+ {
104
+ name: "sitemap:source-urls",
105
+ group: "sitemap",
106
+ base: "sitemap",
107
+ maxAge: SERVER_CACHE_MAX_AGE,
108
+ getKey: (event, key) => {
109
+ const host = getHeader(event, "host") || getHeader(event, "x-forwarded-host") || "";
110
+ const proto = getHeader(event, "x-forwarded-proto") || "https";
111
+ return `source-${proto}-${host}-${hashCacheKey(key)}`;
112
+ },
113
+ swr: true,
114
+ // A failed fetch must never be served again, otherwise one outage empties the sitemap for a
115
+ // whole cache window.
116
+ validate: (entry) => {
117
+ const value = entry.value;
118
+ return value !== void 0 && !value._isFailure;
119
+ }
120
+ }
121
+ );
122
+ function isSourceCacheEnabled() {
123
+ if (import.meta.dev || import.meta.prerender)
124
+ return false;
125
+ const cacheMaxAgeSeconds = staticConfig.cacheMaxAgeSeconds;
126
+ return typeof cacheMaxAgeSeconds === "number" && cacheMaxAgeSeconds > 0;
127
+ }
72
128
  export async function fetchDataSource(input, event) {
73
129
  const context = typeof input.context === "string" ? { name: input.context } : input.context || { name: "fetch" };
74
130
  const url = typeof input.fetch === "string" ? input.fetch : input.fetch[0];
75
131
  const options = typeof input.fetch === "string" ? {} : input.fetch[1];
132
+ const memo = useSourceFetchMemo(event);
133
+ const key = `${url}::${JSON.stringify(options || {})}`;
134
+ let request = memo?.get(key);
135
+ if (!request) {
136
+ request = event && isSourceCacheEnabled() ? fetchSourceUrlsCached(event, key, url, options) : fetchSourceUrls(url, options, event);
137
+ memo?.set(key, request);
138
+ }
139
+ const result = await request;
140
+ if (result._isFailure)
141
+ memo?.delete(key);
142
+ return { ...input, context, ...result };
143
+ }
144
+ async function fetchSourceUrls(url, options, event) {
76
145
  const start = Date.now();
77
146
  const isExternalUrl = !url.startsWith("/");
78
147
  const timeout = isExternalUrl ? 1e4 : options.timeout || 5e3;
@@ -113,11 +182,12 @@ export async function fetchDataSource(input, event) {
113
182
  const timeTakenMs = Date.now() - start;
114
183
  if (isMaybeErrorResponse) {
115
184
  return {
116
- ...input,
117
- context,
118
185
  urls: [],
119
186
  timeTakenMs,
120
- error: "Received HTML response instead of JSON"
187
+ error: "Received HTML response instead of JSON",
188
+ // An HTML page is usually an outage or an auth wall, both transient. Treat it like a
189
+ // failed fetch so the empty result is never cached.
190
+ _isFailure: true
121
191
  };
122
192
  }
123
193
  let urls = [];
@@ -133,8 +203,6 @@ export async function fetchDataSource(input, event) {
133
203
  urls = res.urls || res;
134
204
  }
135
205
  return {
136
- ...input,
137
- context,
138
206
  timeTakenMs,
139
207
  urls
140
208
  };
@@ -154,8 +222,6 @@ export async function fetchDataSource(input, event) {
154
222
  logger.error("Failed to fetch source.", { url, error: error.message });
155
223
  }
156
224
  return {
157
- ...input,
158
- context,
159
225
  urls: [],
160
226
  error: error.message,
161
227
  _isFailure: true
@@ -179,6 +245,15 @@ export async function globalSitemapSources() {
179
245
  return [...m.sources];
180
246
  }
181
247
  export async function childSitemapSources(definition) {
248
+ if (definition?.sources?.length)
249
+ return [...definition.sources];
250
+ if (definition?.urls) {
251
+ const urls = typeof definition.urls === "function" ? await definition.urls() : definition.urls;
252
+ return [{
253
+ context: { name: `sitemaps:${definition.sitemapName}:urls`, description: "Set with the sitemap definition `urls`." },
254
+ urls
255
+ }];
256
+ }
182
257
  if (!definition?._hasSourceChunk)
183
258
  return [];
184
259
  if (import.meta.prerender) {
@@ -2,3 +2,4 @@ import type { H3Event } from '#nuxtseo/h3';
2
2
  import type { ModuleRuntimeConfig } from '../types.js';
3
3
  export * from '../utils-pure.js';
4
4
  export declare function useSitemapRuntimeConfig(e?: H3Event): ModuleRuntimeConfig;
5
+ export declare function useResolvedSitemapRuntimeConfig(e: H3Event): Promise<ModuleRuntimeConfig>;
@@ -1,18 +1,74 @@
1
- import { useRuntimeConfig } from "#nuxtseo/nitro";
1
+ import { getHeader } from "#nuxtseo/h3";
2
+ import { defineCachedFunction, useNitroApp, useRuntimeConfig } from "#nuxtseo/nitro";
2
3
  import staticConfig from "#sitemap-virtual/static-config.mjs";
3
4
  import { normalizeRuntimeFilters } from "../utils-pure.js";
4
5
  export * from "../utils-pure.js";
5
- export function useSitemapRuntimeConfig(e) {
6
- const sitemaps = Object.fromEntries(
6
+ const SERVER_CACHE_MAX_AGE = staticConfig.cacheMaxAgeSeconds || 60 * 10;
7
+ function dynamicRuntimeConfig(e) {
8
+ return useRuntimeConfig(e).sitemap;
9
+ }
10
+ function copyStaticSitemaps() {
11
+ return Object.fromEntries(
7
12
  Object.entries(staticConfig.sitemaps).map(([name, sitemap]) => [name, {
8
13
  ...sitemap,
9
14
  include: normalizeRuntimeFilters("include" in sitemap ? sitemap.include : void 0),
10
15
  exclude: normalizeRuntimeFilters("exclude" in sitemap ? sitemap.exclude : void 0)
11
16
  }])
12
17
  );
18
+ }
19
+ export function useSitemapRuntimeConfig(e) {
20
+ return Object.freeze({
21
+ ...staticConfig,
22
+ sitemaps: copyStaticSitemaps(),
23
+ ...dynamicRuntimeConfig(e)
24
+ });
25
+ }
26
+ function serializeFilters(filters) {
27
+ if (!filters?.length)
28
+ return void 0;
29
+ return filters.map((f) => {
30
+ if (f instanceof RegExp)
31
+ return { regex: `/${f.source}/${f.flags}` };
32
+ return f;
33
+ });
34
+ }
35
+ async function resolveSitemapSitemaps(e, nitro) {
36
+ const ctx = { sitemaps: copyStaticSitemaps(), event: e };
37
+ await nitro.hooks.callHook("sitemap:sitemaps-resolved", ctx);
38
+ const sitemaps = { ...ctx.sitemaps };
39
+ for (const name of Object.keys(sitemaps)) {
40
+ const sitemap = { ...sitemaps[name] };
41
+ if (typeof sitemap.urls === "function")
42
+ sitemap.urls = await sitemap.urls();
43
+ sitemap.include = serializeFilters(sitemap.include);
44
+ sitemap.exclude = serializeFilters(sitemap.exclude);
45
+ sitemaps[name] = sitemap;
46
+ }
47
+ return sitemaps;
48
+ }
49
+ const resolveSitemapSitemapsCached = defineCachedFunction(
50
+ resolveSitemapSitemaps,
51
+ {
52
+ name: "sitemap:runtime-sitemaps",
53
+ group: "sitemap",
54
+ maxAge: SERVER_CACHE_MAX_AGE,
55
+ base: "sitemap",
56
+ // nitro calls getKey with the full fn args (event, nitro)
57
+ getKey: (e) => {
58
+ const host = e && (getHeader(e, "host") || getHeader(e, "x-forwarded-host")) || "";
59
+ const proto = e && getHeader(e, "x-forwarded-proto") || "https";
60
+ return `runtime-sitemaps-${proto}-${host}`;
61
+ },
62
+ swr: true
63
+ }
64
+ );
65
+ export async function useResolvedSitemapRuntimeConfig(e) {
66
+ const maxAge = dynamicRuntimeConfig(e)?.cacheMaxAgeSeconds ?? staticConfig.cacheMaxAgeSeconds;
67
+ const shouldCache = !import.meta.dev && !import.meta.prerender && typeof maxAge === "number" && maxAge > 0;
68
+ const sitemaps = shouldCache ? await resolveSitemapSitemapsCached(e, useNitroApp()) : await resolveSitemapSitemaps(e, useNitroApp());
13
69
  return Object.freeze({
14
70
  ...staticConfig,
15
71
  sitemaps,
16
- ...useRuntimeConfig(e).sitemap
72
+ ...dynamicRuntimeConfig(e)
17
73
  });
18
74
  }
@@ -204,7 +204,7 @@ export interface SitemapSourceResolved extends Omit<SitemapSourceBase, 'urls'> {
204
204
  message: string;
205
205
  }[];
206
206
  }
207
- export type AppSourceContext = 'nuxt:pages' | 'nuxt:prerender' | 'nuxt:route-rules' | '@nuxtjs/i18n:pages' | 'nuxt-i18n-micro:pages' | '@nuxt/content@v2:urls' | '@nuxt/content@v3:urls';
207
+ export type AppSourceContext = 'nuxt:pages' | 'nuxt:prerender' | 'nuxt:route-rules' | '@nuxtjs/i18n:pages' | 'nuxt-i18n-micro:pages' | '@nuxt/content@v2:urls' | '@nuxt/content@v3:urls' | '@harlan-zw/comark-content:urls';
208
208
  export type SitemapSourceInput = string | [string, FetchOptions] | SitemapSourceBase | SitemapSourceResolved;
209
209
  interface LocaleObject extends Record<string, any> {
210
210
  code: string;
@@ -245,7 +245,7 @@ export interface ModuleRuntimeConfig extends Pick<ModuleOptions, 'sitemapsPathPr
245
245
  index?: Pick<SitemapDefinition, 'sitemapName' | '_route'> & {
246
246
  sitemaps: SitemapIndexEntry[];
247
247
  };
248
- } & Record<string, Omit<SitemapDefinition, 'urls'> & {
248
+ } & Record<string, SitemapDefinition & {
249
249
  _hasSourceChunk?: boolean;
250
250
  }>;
251
251
  autoI18n?: AutoI18nConfig;
@@ -398,6 +398,20 @@ export interface SitemapSourcesHookCtx<Event = H3Event> extends NitroBaseHook<Ev
398
398
  sitemapName: string;
399
399
  sources: SitemapSourceInput[];
400
400
  }
401
+ export interface SitemapsResolvedCtx<Event = H3Event> extends NitroBaseHook<Event> {
402
+ /**
403
+ * The sitemaps config about to be used to build the sitemap index and serve child
404
+ * sitemaps. Static definitions from nuxt.config are already present.
405
+ *
406
+ * Push definitions here to register sitemaps at runtime, for example when the data set
407
+ * grows while the server is running. Delete a key to remove its sitemap from the index
408
+ * and stop serving it. Registered sitemaps accept the same fields as nuxt.config
409
+ * definitions (`sources`, `urls`, `include`, `exclude`, `defaults`, `chunks`) and are
410
+ * served, listed in the sitemap index, and passed the `sitemap:sources` hook like
411
+ * static ones.
412
+ */
413
+ sitemaps: ModuleRuntimeConfig['sitemaps'];
414
+ }
401
415
  export type Changefreq = 'always' | 'hourly' | 'daily' | 'weekly' | 'monthly' | 'yearly' | 'never';
402
416
  export interface SitemapUrl {
403
417
  loc: string;
@@ -413,6 +427,10 @@ export interface SitemapUrl {
413
427
  images?: Array<ImageEntry>;
414
428
  videos?: Array<VideoEntry>;
415
429
  _i18nTransform?: boolean;
430
+ /**
431
+ * Route this URL to a specific sitemap. The name must exist in the sitemap config,
432
+ * either set in `nuxt.config` or registered with the `sitemap:sitemaps-resolved` hook.
433
+ */
416
434
  _sitemap?: string | false;
417
435
  /**
418
436
  * Mark the URL as already encoded.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@nuxtjs/sitemap",
3
3
  "type": "module",
4
- "version": "8.3.4",
4
+ "version": "8.5.0",
5
5
  "description": "Powerfully flexible XML Sitemaps that integrate seamlessly, for Nuxt.",
6
6
  "author": {
7
7
  "name": "Harlan Wilton",
@@ -58,42 +58,43 @@
58
58
  "@nuxt/kit": "^4.5.2",
59
59
  "consola": "^3.4.2",
60
60
  "defu": "^6.1.7",
61
- "nuxt-site-config": "^4.2.0",
62
- "nuxtseo-shared": "^5.3.11",
61
+ "nuxt-site-config": "^4.2.3",
62
+ "nuxtseo-shared": "^5.3.14",
63
63
  "ofetch": "^1.5.1",
64
64
  "pathe": "^2.0.3",
65
65
  "pkg-types": "^2.3.1",
66
66
  "radix3": "^1.1.2",
67
67
  "ufo": "^1.6.4",
68
68
  "ultrahtml": "^1.7.0",
69
- "sitemapd": "^0.2.1"
69
+ "sitemapd": "^0.2.2"
70
70
  },
71
71
  "devDependencies": {
72
72
  "@antfu/eslint-config": "^9.3.0",
73
73
  "@arethetypeswrong/cli": "^0.18.5",
74
+ "@harlan-zw/comark-content": "^0.1.3",
74
75
  "@nuxt/content": "^3.15.2",
75
- "@nuxt/devtools-kit": "4.0.0-alpha.9",
76
+ "@nuxt/devtools-kit": "4.0.0-alpha.12",
76
77
  "@nuxt/module-builder": "^1.0.3",
77
78
  "@nuxt/test-utils": "^4.1.0",
78
79
  "@nuxt/ui": "^4.10.0",
79
80
  "@nuxtjs/i18n": "^10.6.0",
80
- "@nuxtjs/robots": "^6.1.4",
81
+ "@nuxtjs/robots": "^6.1.5",
81
82
  "@vue/test-utils": "^2.4.11",
82
83
  "better-sqlite3": "^13.0.3",
83
- "bumpp": "^12.2.0",
84
+ "bumpp": "^12.2.1",
84
85
  "eslint": "^10.8.1",
85
- "eslint-plugin-harlanzw": "^0.17.1",
86
- "happy-dom": "^20.11.2",
86
+ "eslint-plugin-harlanzw": "^0.20.0",
87
+ "happy-dom": "^20.11.6",
87
88
  "nuxt": "^4.5.2",
88
89
  "nuxt-i18n-micro": "^3.28.0",
89
- "nuxtseo-layer-devtools": "^5.3.11",
90
+ "nuxtseo-layer-devtools": "^5.3.14",
90
91
  "std-env": "^4.2.0",
91
92
  "typescript": "6.0.3",
92
93
  "unbuild": "^3.6.1",
93
- "vitest": "^4.1.10",
94
- "vue-tsc": "^3.3.9",
94
+ "vitest": "^4.1.11",
95
+ "vue-tsc": "^3.3.10",
95
96
  "zod": "^4.4.3",
96
- "@nuxtjs/sitemap": "8.3.4"
97
+ "@nuxtjs/sitemap": "8.5.0"
97
98
  },
98
99
  "scripts": {
99
100
  "lint": "eslint .",
@@ -1,2 +0,0 @@
1
- declare const _default: any;
2
- export default _default;
@@ -1,40 +0,0 @@
1
- import { queryCollection } from "@nuxt/content/server";
2
- import manifest from "#content/manifest";
3
- import { defineEventHandler } from "#nuxtseo/h3";
4
- import { filters } from "#sitemap/content-filters";
5
- import { onUrlFns } from "#sitemap/content-on-url";
6
- export default defineEventHandler(async (e) => {
7
- const collections = [];
8
- for (const collection in manifest) {
9
- if (manifest[collection].fields.sitemap)
10
- collections.push(collection);
11
- }
12
- const contentList = [];
13
- for (const collection of collections) {
14
- const needsAllFields = filters?.has(collection) || onUrlFns?.has(collection);
15
- const query = queryCollection(e, collection).where("path", "IS NOT NULL").where("sitemap", "IS NOT NULL");
16
- if (!needsAllFields)
17
- query.select("path", "sitemap");
18
- contentList.push(
19
- query.all().then((results2) => {
20
- const filter = filters?.get(collection);
21
- return { collection, entries: filter ? results2.filter(filter) : results2 };
22
- }).catch((err) => {
23
- console.error(`[@nuxtjs/sitemap] Couldn't query @nuxt/content collection "${collection}" for the sitemap, so its URLs will be missing. On serverless the content DB is restored from a prerendered sql_dump.txt that isn't readable inside the function (nuxt/content#3805). Fix: prerender the sitemap so content URLs resolve at build, or configure a runtime database (D1/Turso/Postgres).`, err);
24
- return { collection, entries: [] };
25
- })
26
- );
27
- }
28
- const results = await Promise.all(contentList);
29
- return results.flatMap(({ collection, entries }) => {
30
- const onUrl = onUrlFns?.get(collection);
31
- return entries.filter((c) => c.sitemap !== false && c.path && !c.path.endsWith(".navigation")).map((c) => {
32
- const url = {
33
- loc: c.path,
34
- ...typeof c.sitemap === "object" ? c.sitemap : {}
35
- };
36
- onUrl?.(url, c, collection);
37
- return url;
38
- });
39
- }).filter(Boolean);
40
- });