@saasmakers/ui 1.4.38 → 1.4.40
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/app/types/global.d.ts +1 -1
- package/nuxt.config.ts +14 -0
- package/package.json +42 -36
- package/server/patches/nuxt-seo/robots-injectContext.js +35 -0
- package/server/patches/nuxt-seo/routes/sitemap.xsl.js +452 -0
- package/server/patches/nuxt-seo/sitemap/builder/sitemap-index.js +145 -0
- package/server/patches/nuxt-seo/sitemap/builder/sitemap.js +276 -0
- package/server/patches/nuxt-seo/sitemap/event-handlers.js +87 -0
- package/server/patches/nuxt-seo/sitemap/nitro.js +157 -0
- package/server/patches/nuxt-seo/utils/h3-compat.js +55 -0
- package/server/patches/robots-injectContext.js +35 -0
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import { safeGetHeader } from "../../utils/h3-compat.js";
|
|
2
|
+
import { defineCachedFunction } from "nitropack/runtime";
|
|
3
|
+
import { joinURL, withQuery } from "ufo";
|
|
4
|
+
import staticConfig from "#sitemap-virtual/static-config.mjs";
|
|
5
|
+
import { normaliseDate } from "../urlset/normalise.js";
|
|
6
|
+
import { getResolvedSitemapUrls } from "./sitemap.js";
|
|
7
|
+
import { escapeValueForXml } from "./xml.js";
|
|
8
|
+
const SERVER_CACHE_MAX_AGE = staticConfig.cacheMaxAgeSeconds || 60 * 10;
|
|
9
|
+
const buildSitemapIndexCached = defineCachedFunction(
|
|
10
|
+
async (event, resolvers, runtimeConfig, nitro) => {
|
|
11
|
+
return buildSitemapIndexInternal(resolvers, runtimeConfig, nitro);
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
name: "sitemap:index",
|
|
15
|
+
group: "sitemap",
|
|
16
|
+
maxAge: SERVER_CACHE_MAX_AGE,
|
|
17
|
+
base: "sitemap",
|
|
18
|
+
// Use the sitemap storage
|
|
19
|
+
getKey: (event) => {
|
|
20
|
+
const host = safeGetHeader(event, "host") || safeGetHeader(event, "x-forwarded-host") || "";
|
|
21
|
+
const proto = safeGetHeader(event, "x-forwarded-proto") || "https";
|
|
22
|
+
return `sitemap-index-${proto}-${host}`;
|
|
23
|
+
},
|
|
24
|
+
swr: true
|
|
25
|
+
// Enable stale-while-revalidate
|
|
26
|
+
}
|
|
27
|
+
);
|
|
28
|
+
async function buildSitemapIndexInternal(resolvers, runtimeConfig, nitro) {
|
|
29
|
+
const {
|
|
30
|
+
sitemaps,
|
|
31
|
+
autoLastmod,
|
|
32
|
+
defaultSitemapsChunkSize,
|
|
33
|
+
sitemapsPathPrefix
|
|
34
|
+
} = runtimeConfig;
|
|
35
|
+
if (!sitemaps)
|
|
36
|
+
throw new Error("Attempting to build a sitemap index without required `sitemaps` configuration.");
|
|
37
|
+
const nonChunkedNames = [];
|
|
38
|
+
const allFailedSources = [];
|
|
39
|
+
for (const sitemapName in sitemaps) {
|
|
40
|
+
if (sitemapName === "index" || sitemapName === "chunks")
|
|
41
|
+
continue;
|
|
42
|
+
const sitemapConfig = sitemaps[sitemapName];
|
|
43
|
+
if (sitemapConfig.chunks || sitemapConfig._isChunking) {
|
|
44
|
+
sitemapConfig._isChunking = true;
|
|
45
|
+
sitemapConfig._chunkSize = sitemapConfig.chunkSize || (typeof sitemapConfig.chunks === "number" ? sitemapConfig.chunks : defaultSitemapsChunkSize || 1e3);
|
|
46
|
+
} else {
|
|
47
|
+
nonChunkedNames.push(sitemapName);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
const indexLastmod = autoLastmod ? normaliseDate(/* @__PURE__ */ new Date()) : void 0;
|
|
51
|
+
const entries = [];
|
|
52
|
+
if (typeof sitemaps.chunks !== "undefined") {
|
|
53
|
+
const sitemap = sitemaps.chunks;
|
|
54
|
+
const resolved = await getResolvedSitemapUrls(sitemap, "sitemap", true, resolvers, runtimeConfig, nitro);
|
|
55
|
+
allFailedSources.push(...resolved.failedSources);
|
|
56
|
+
const chunkCount = Math.ceil(resolved.urls.length / defaultSitemapsChunkSize);
|
|
57
|
+
for (let i = 0; i < chunkCount; i++) {
|
|
58
|
+
const entry = {
|
|
59
|
+
_sitemapName: String(i),
|
|
60
|
+
sitemap: resolvers.canonicalUrlResolver(joinURL(sitemapsPathPrefix || "", `/${i}.xml`))
|
|
61
|
+
};
|
|
62
|
+
if (indexLastmod)
|
|
63
|
+
entry.lastmod = indexLastmod;
|
|
64
|
+
entries.push(entry);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
for (const name of nonChunkedNames) {
|
|
68
|
+
const entry = {
|
|
69
|
+
_sitemapName: name,
|
|
70
|
+
sitemap: resolvers.canonicalUrlResolver(joinURL(sitemapsPathPrefix || "", `/${name}.xml`))
|
|
71
|
+
};
|
|
72
|
+
if (indexLastmod)
|
|
73
|
+
entry.lastmod = indexLastmod;
|
|
74
|
+
entries.push(entry);
|
|
75
|
+
}
|
|
76
|
+
for (const sitemapName in sitemaps) {
|
|
77
|
+
const sitemapConfig = sitemaps[sitemapName];
|
|
78
|
+
if (sitemapName !== "index" && sitemapConfig._isChunking) {
|
|
79
|
+
const chunkSize = sitemapConfig._chunkSize || defaultSitemapsChunkSize || 1e3;
|
|
80
|
+
let chunkCount;
|
|
81
|
+
if (typeof sitemapConfig.chunkCount === "number" && sitemapConfig.chunkCount > 0) {
|
|
82
|
+
chunkCount = sitemapConfig.chunkCount;
|
|
83
|
+
} else {
|
|
84
|
+
const resolved = await getResolvedSitemapUrls(sitemapConfig, sitemapName, true, resolvers, runtimeConfig, nitro);
|
|
85
|
+
allFailedSources.push(...resolved.failedSources);
|
|
86
|
+
chunkCount = Math.ceil(resolved.urls.length / chunkSize);
|
|
87
|
+
}
|
|
88
|
+
sitemapConfig._chunkCount = chunkCount;
|
|
89
|
+
for (let i = 0; i < chunkCount; i++) {
|
|
90
|
+
const chunkName = `${sitemapName}-${i}`;
|
|
91
|
+
const entry = {
|
|
92
|
+
_sitemapName: chunkName,
|
|
93
|
+
sitemap: resolvers.canonicalUrlResolver(joinURL(sitemapsPathPrefix || "", `/${chunkName}.xml`))
|
|
94
|
+
};
|
|
95
|
+
if (indexLastmod)
|
|
96
|
+
entry.lastmod = indexLastmod;
|
|
97
|
+
entries.push(entry);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
if (sitemaps.index) {
|
|
102
|
+
entries.push(...sitemaps.index.sitemaps.map((entry) => {
|
|
103
|
+
return typeof entry === "string" ? { sitemap: entry } : entry;
|
|
104
|
+
}));
|
|
105
|
+
}
|
|
106
|
+
return { entries, failedSources: allFailedSources };
|
|
107
|
+
}
|
|
108
|
+
export function urlsToIndexXml(sitemaps, resolvers, { version, xsl, credits, minify }, errorInfo) {
|
|
109
|
+
const sitemapXml = sitemaps.map((e) => [
|
|
110
|
+
" <sitemap>",
|
|
111
|
+
` <loc>${escapeValueForXml(e.sitemap)}</loc>`,
|
|
112
|
+
// lastmod is optional
|
|
113
|
+
e.lastmod ? ` <lastmod>${escapeValueForXml(e.lastmod)}</lastmod>` : false,
|
|
114
|
+
" </sitemap>"
|
|
115
|
+
].filter(Boolean).join("\n")).join("\n");
|
|
116
|
+
const xmlParts = [
|
|
117
|
+
'<?xml version="1.0" encoding="UTF-8"?>'
|
|
118
|
+
];
|
|
119
|
+
if (xsl) {
|
|
120
|
+
let relativeBaseUrl = resolvers.relativeBaseUrlResolver?.(xsl) ?? xsl;
|
|
121
|
+
if (errorInfo && errorInfo.messages.length > 0) {
|
|
122
|
+
relativeBaseUrl = withQuery(relativeBaseUrl, {
|
|
123
|
+
errors: "true",
|
|
124
|
+
error_messages: errorInfo.messages,
|
|
125
|
+
error_urls: errorInfo.urls
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
xmlParts.push(`<?xml-stylesheet type="text/xsl" href="${escapeValueForXml(relativeBaseUrl)}"?>`);
|
|
129
|
+
}
|
|
130
|
+
xmlParts.push(
|
|
131
|
+
'<sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">',
|
|
132
|
+
sitemapXml,
|
|
133
|
+
"</sitemapindex>"
|
|
134
|
+
);
|
|
135
|
+
if (credits) {
|
|
136
|
+
xmlParts.push(`<!-- XML Sitemap Index generated by @nuxtjs/sitemap v${version} at ${(/* @__PURE__ */ new Date()).toISOString()} -->`);
|
|
137
|
+
}
|
|
138
|
+
return minify ? xmlParts.join("").replace(/(?<!<[^>]*)\s(?![^<]*>)/g, "") : xmlParts.join("\n");
|
|
139
|
+
}
|
|
140
|
+
export async function buildSitemapIndex(resolvers, runtimeConfig, nitro) {
|
|
141
|
+
if (!import.meta.dev && !import.meta.prerender && typeof runtimeConfig.cacheMaxAgeSeconds === "number" && runtimeConfig.cacheMaxAgeSeconds > 0 && resolvers.event) {
|
|
142
|
+
return buildSitemapIndexCached(resolvers.event, resolvers, runtimeConfig, nitro);
|
|
143
|
+
}
|
|
144
|
+
return buildSitemapIndexInternal(resolvers, runtimeConfig, nitro);
|
|
145
|
+
}
|
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
import { safeGetHeader } from "../../utils/h3-compat.js";
|
|
2
|
+
import { defineCachedFunction, useRuntimeConfig } from "nitropack/runtime";
|
|
3
|
+
import { resolveSitePath } from "nuxt-site-config/urls";
|
|
4
|
+
import { joinURL, withHttps } from "ufo";
|
|
5
|
+
import staticConfig from "#sitemap-virtual/static-config.mjs";
|
|
6
|
+
import { applyDynamicParams, createPathFilter, findPageMapping, logger, resolveI18nSitemapLocaleKey, splitForLocales } from "../../../utils-pure.js";
|
|
7
|
+
import { preNormalizeEntry } from "../urlset/normalise.js";
|
|
8
|
+
import { sortInPlace } from "../urlset/sort.js";
|
|
9
|
+
import { childSitemapSources, globalSitemapSources, resolveSitemapSources } from "../urlset/sources.js";
|
|
10
|
+
import { parseChunkInfo, sliceUrlsForChunk } from "../utils/chunk.js";
|
|
11
|
+
const SERVER_CACHE_MAX_AGE = staticConfig.cacheMaxAgeSeconds || 60 * 10;
|
|
12
|
+
export function resolveSitemapEntries(sitemap, urls, runtimeConfig, resolvers, baseURL) {
|
|
13
|
+
const {
|
|
14
|
+
autoI18n,
|
|
15
|
+
isI18nMapped
|
|
16
|
+
} = runtimeConfig;
|
|
17
|
+
const filterPath = createPathFilter({
|
|
18
|
+
include: sitemap.include,
|
|
19
|
+
exclude: sitemap.exclude
|
|
20
|
+
}, baseURL || "/");
|
|
21
|
+
const _urls = urls.map((_e) => {
|
|
22
|
+
const e = preNormalizeEntry(_e, resolvers);
|
|
23
|
+
if (!e.loc || !filterPath(e.loc))
|
|
24
|
+
return false;
|
|
25
|
+
return e;
|
|
26
|
+
}).filter(Boolean);
|
|
27
|
+
let validI18nUrlsForTransform = [];
|
|
28
|
+
const withoutPrefixPaths = {};
|
|
29
|
+
if (autoI18n && autoI18n.strategy !== "no_prefix") {
|
|
30
|
+
const localeCodes = autoI18n.locales.map((l) => l.code);
|
|
31
|
+
const localeByCode = new Map(autoI18n.locales.map((l) => [l.code, l]));
|
|
32
|
+
const isPrefixStrategy = autoI18n.strategy === "prefix";
|
|
33
|
+
const isPrefixExceptOrAndDefault = autoI18n.strategy === "prefix_and_default" || autoI18n.strategy === "prefix_except_default";
|
|
34
|
+
const xDefaultAndLocales = [{ code: "x-default", _hreflang: "x-default" }, ...autoI18n.locales];
|
|
35
|
+
const defaultLocale = autoI18n.defaultLocale;
|
|
36
|
+
const hasPages = !!autoI18n.pages;
|
|
37
|
+
const hasDifferentDomains = !!autoI18n.differentDomains;
|
|
38
|
+
validI18nUrlsForTransform = _urls.map((_e, i) => {
|
|
39
|
+
if (_e._abs)
|
|
40
|
+
return false;
|
|
41
|
+
const split = splitForLocales(_e._relativeLoc, localeCodes);
|
|
42
|
+
let localeCode = split[0];
|
|
43
|
+
const pathWithoutPrefix = split[1];
|
|
44
|
+
if (!localeCode)
|
|
45
|
+
localeCode = defaultLocale;
|
|
46
|
+
const e = _e;
|
|
47
|
+
e._pathWithoutPrefix = pathWithoutPrefix;
|
|
48
|
+
const locale = localeByCode.get(localeCode);
|
|
49
|
+
if (!locale)
|
|
50
|
+
return false;
|
|
51
|
+
e._locale = locale;
|
|
52
|
+
e._index = i;
|
|
53
|
+
e._key = `${e._sitemap || ""}${e._path?.pathname || "/"}${e._path?.search || ""}`;
|
|
54
|
+
withoutPrefixPaths[pathWithoutPrefix] = withoutPrefixPaths[pathWithoutPrefix] || [];
|
|
55
|
+
if (!withoutPrefixPaths[pathWithoutPrefix].some((e2) => e2._locale.code === locale.code))
|
|
56
|
+
withoutPrefixPaths[pathWithoutPrefix].push(e);
|
|
57
|
+
return e;
|
|
58
|
+
}).filter(Boolean);
|
|
59
|
+
for (const e of validI18nUrlsForTransform) {
|
|
60
|
+
if (!e._i18nTransform && !e.alternatives?.length) {
|
|
61
|
+
const alternatives = (withoutPrefixPaths[e._pathWithoutPrefix] || []).map((u) => {
|
|
62
|
+
const entries = [];
|
|
63
|
+
if (u._locale.code === defaultLocale) {
|
|
64
|
+
entries.push({
|
|
65
|
+
href: u.loc,
|
|
66
|
+
hreflang: "x-default"
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
entries.push({
|
|
70
|
+
href: u.loc,
|
|
71
|
+
hreflang: u._locale._hreflang || defaultLocale
|
|
72
|
+
});
|
|
73
|
+
return entries;
|
|
74
|
+
}).flat().filter(Boolean);
|
|
75
|
+
if (alternatives.length)
|
|
76
|
+
e.alternatives = alternatives;
|
|
77
|
+
} else if (e._i18nTransform) {
|
|
78
|
+
delete e._i18nTransform;
|
|
79
|
+
if (hasDifferentDomains) {
|
|
80
|
+
const defLocale = localeByCode.get(defaultLocale);
|
|
81
|
+
e.alternatives = [
|
|
82
|
+
{
|
|
83
|
+
...defLocale,
|
|
84
|
+
code: "x-default"
|
|
85
|
+
},
|
|
86
|
+
...autoI18n.locales.filter((l) => !!l.domain)
|
|
87
|
+
].map((locale) => {
|
|
88
|
+
return {
|
|
89
|
+
hreflang: locale._hreflang,
|
|
90
|
+
href: joinURL(withHttps(locale.domain), e._pathWithoutPrefix)
|
|
91
|
+
};
|
|
92
|
+
});
|
|
93
|
+
} else {
|
|
94
|
+
const pageMatch = hasPages ? findPageMapping(e._pathWithoutPrefix, autoI18n.pages) : null;
|
|
95
|
+
const pathSearch = e._path?.search || "";
|
|
96
|
+
const pathWithoutPrefix = e._pathWithoutPrefix;
|
|
97
|
+
for (const l of autoI18n.locales) {
|
|
98
|
+
let loc = pathWithoutPrefix;
|
|
99
|
+
if (pageMatch && pageMatch.mappings[l.code] !== void 0) {
|
|
100
|
+
const customPath = pageMatch.mappings[l.code];
|
|
101
|
+
if (customPath === false)
|
|
102
|
+
continue;
|
|
103
|
+
if (typeof customPath === "string") {
|
|
104
|
+
loc = customPath[0] === "/" ? customPath : `/${customPath}`;
|
|
105
|
+
loc = applyDynamicParams(loc, pageMatch.paramSegments);
|
|
106
|
+
if (isPrefixStrategy || isPrefixExceptOrAndDefault && l.code !== defaultLocale)
|
|
107
|
+
loc = joinURL(`/${l.code}`, loc);
|
|
108
|
+
}
|
|
109
|
+
} else if (!hasDifferentDomains && !(isPrefixExceptOrAndDefault && l.code === defaultLocale)) {
|
|
110
|
+
loc = joinURL(`/${l.code}`, pathWithoutPrefix);
|
|
111
|
+
}
|
|
112
|
+
const _sitemap = isI18nMapped ? l._sitemap : void 0;
|
|
113
|
+
const alternatives = [];
|
|
114
|
+
for (const locale of xDefaultAndLocales) {
|
|
115
|
+
const code = locale.code === "x-default" ? defaultLocale : locale.code;
|
|
116
|
+
const isDefault = locale.code === "x-default" || locale.code === defaultLocale;
|
|
117
|
+
let href = pathWithoutPrefix;
|
|
118
|
+
if (pageMatch && pageMatch.mappings[code] !== void 0) {
|
|
119
|
+
const customPath = pageMatch.mappings[code];
|
|
120
|
+
if (customPath === false)
|
|
121
|
+
continue;
|
|
122
|
+
if (typeof customPath === "string") {
|
|
123
|
+
href = customPath[0] === "/" ? customPath : `/${customPath}`;
|
|
124
|
+
href = applyDynamicParams(href, pageMatch.paramSegments);
|
|
125
|
+
if (isPrefixStrategy || isPrefixExceptOrAndDefault && !isDefault)
|
|
126
|
+
href = joinURL("/", code, href);
|
|
127
|
+
}
|
|
128
|
+
} else if (isPrefixStrategy) {
|
|
129
|
+
href = joinURL("/", code, pathWithoutPrefix);
|
|
130
|
+
} else if (isPrefixExceptOrAndDefault && !isDefault) {
|
|
131
|
+
href = joinURL("/", code, pathWithoutPrefix);
|
|
132
|
+
}
|
|
133
|
+
if (!filterPath(href))
|
|
134
|
+
continue;
|
|
135
|
+
alternatives.push({
|
|
136
|
+
hreflang: locale._hreflang,
|
|
137
|
+
href
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
const { _index: _, ...rest } = e;
|
|
141
|
+
const newEntry = preNormalizeEntry({
|
|
142
|
+
_sitemap,
|
|
143
|
+
...rest,
|
|
144
|
+
_key: `${_sitemap || ""}${loc || "/"}${pathSearch}`,
|
|
145
|
+
_locale: l,
|
|
146
|
+
loc,
|
|
147
|
+
alternatives
|
|
148
|
+
}, resolvers);
|
|
149
|
+
if (e._locale.code === newEntry._locale.code) {
|
|
150
|
+
_urls[e._index] = newEntry;
|
|
151
|
+
e._index = void 0;
|
|
152
|
+
} else {
|
|
153
|
+
_urls.push(newEntry);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
if (isI18nMapped) {
|
|
159
|
+
e._sitemap = e._sitemap || e._locale._sitemap;
|
|
160
|
+
e._key = `${e._sitemap || ""}${e.loc || "/"}${e._path?.search || ""}`;
|
|
161
|
+
}
|
|
162
|
+
if (e._index)
|
|
163
|
+
_urls[e._index] = e;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
return _urls;
|
|
167
|
+
}
|
|
168
|
+
export async function buildResolvedSitemapUrls(effectiveSitemap, matchName, isChunked, resolvers, runtimeConfig, nitro) {
|
|
169
|
+
const { sitemaps, autoI18n, isI18nMapped, isMultiSitemap, sortEntries } = runtimeConfig;
|
|
170
|
+
let sourcesInput = effectiveSitemap.includeAppSources ? [...await globalSitemapSources(), ...await childSitemapSources(effectiveSitemap)] : await childSitemapSources(effectiveSitemap);
|
|
171
|
+
if (nitro && resolvers.event) {
|
|
172
|
+
const ctx = {
|
|
173
|
+
event: resolvers.event,
|
|
174
|
+
sitemapName: matchName,
|
|
175
|
+
sources: sourcesInput
|
|
176
|
+
};
|
|
177
|
+
await nitro.hooks.callHook("sitemap:sources", ctx);
|
|
178
|
+
sourcesInput = ctx.sources;
|
|
179
|
+
}
|
|
180
|
+
const sources = await resolveSitemapSources(sourcesInput, resolvers.event);
|
|
181
|
+
const failedSources = sources.filter((source) => source.error && source._isFailure).map((source) => ({
|
|
182
|
+
url: typeof source.fetch === "string" ? source.fetch : source.fetch?.[0] || "unknown",
|
|
183
|
+
error: source.error || "Unknown error"
|
|
184
|
+
}));
|
|
185
|
+
const resolvedCtx = {
|
|
186
|
+
urls: sources.flatMap((s) => s.urls),
|
|
187
|
+
sitemapName: matchName,
|
|
188
|
+
event: resolvers.event
|
|
189
|
+
};
|
|
190
|
+
await nitro?.hooks.callHook("sitemap:input", resolvedCtx);
|
|
191
|
+
const enhancedUrls = resolveSitemapEntries(effectiveSitemap, resolvedCtx.urls, { autoI18n, isI18nMapped }, resolvers, useRuntimeConfig().app.baseURL);
|
|
192
|
+
const localeSitemapKeys = isI18nMapped && autoI18n ? autoI18n.locales.map((l) => l._sitemap) : [];
|
|
193
|
+
if (isMultiSitemap) {
|
|
194
|
+
const sitemapNames = Object.keys(sitemaps).filter((k) => k !== "index");
|
|
195
|
+
const warnedSitemaps = nitro?._sitemapWarnedSitemaps || /* @__PURE__ */ new Set();
|
|
196
|
+
for (const e of enhancedUrls) {
|
|
197
|
+
const hasMatchingSitemap = typeof e._sitemap === "string" && (sitemapNames.includes(e._sitemap) || isI18nMapped && sitemapNames.some((name) => resolveI18nSitemapLocaleKey(name, localeSitemapKeys) === e._sitemap));
|
|
198
|
+
if (typeof e._sitemap === "string" && !hasMatchingSitemap) {
|
|
199
|
+
if (!warnedSitemaps.has(e._sitemap)) {
|
|
200
|
+
warnedSitemaps.add(e._sitemap);
|
|
201
|
+
logger.error(`Sitemap \`${e._sitemap}\` not found in sitemap config. Available sitemaps: ${sitemapNames.join(", ")}. Entry \`${e.loc}\` will be omitted.`);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
if (nitro) {
|
|
206
|
+
nitro._sitemapWarnedSitemaps = warnedSitemaps;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
const filteredUrls = enhancedUrls.filter((e) => {
|
|
210
|
+
if (e._sitemap === false)
|
|
211
|
+
return false;
|
|
212
|
+
if (isMultiSitemap && e._sitemap && matchName) {
|
|
213
|
+
if (isChunked)
|
|
214
|
+
return e._sitemap === matchName;
|
|
215
|
+
if (e._sitemap === matchName)
|
|
216
|
+
return true;
|
|
217
|
+
if (isI18nMapped)
|
|
218
|
+
return e._sitemap === resolveI18nSitemapLocaleKey(matchName, localeSitemapKeys);
|
|
219
|
+
return false;
|
|
220
|
+
}
|
|
221
|
+
return true;
|
|
222
|
+
});
|
|
223
|
+
const urls = sortEntries ? sortInPlace(filteredUrls) : filteredUrls;
|
|
224
|
+
return { urls, failedSources };
|
|
225
|
+
}
|
|
226
|
+
export const buildResolvedSitemapUrlsCached = defineCachedFunction(
|
|
227
|
+
async (_event, effectiveSitemap, matchName, isChunked, resolvers, runtimeConfig, nitro) => buildResolvedSitemapUrls(effectiveSitemap, matchName, isChunked, resolvers, runtimeConfig, nitro),
|
|
228
|
+
{
|
|
229
|
+
name: "sitemap:resolved-urls",
|
|
230
|
+
group: "sitemap",
|
|
231
|
+
base: "sitemap",
|
|
232
|
+
maxAge: SERVER_CACHE_MAX_AGE,
|
|
233
|
+
getKey: (event, _effectiveSitemap, matchName, isChunked) => {
|
|
234
|
+
const host = safeGetHeader(event, "host") || safeGetHeader(event, "x-forwarded-host") || "";
|
|
235
|
+
const proto = safeGetHeader(event, "x-forwarded-proto") || "https";
|
|
236
|
+
return `resolved-${isChunked ? "chunked-" : ""}${matchName}-${proto}-${host}`;
|
|
237
|
+
},
|
|
238
|
+
swr: true
|
|
239
|
+
}
|
|
240
|
+
);
|
|
241
|
+
export async function getResolvedSitemapUrls(effectiveSitemap, matchName, isChunked, resolvers, runtimeConfig, nitro) {
|
|
242
|
+
const event = resolvers.event;
|
|
243
|
+
const shouldCache = !import.meta.dev && !import.meta.prerender && typeof runtimeConfig.cacheMaxAgeSeconds === "number" && runtimeConfig.cacheMaxAgeSeconds > 0;
|
|
244
|
+
if (shouldCache && event) {
|
|
245
|
+
return buildResolvedSitemapUrlsCached(event, effectiveSitemap, matchName, isChunked, resolvers, runtimeConfig, nitro);
|
|
246
|
+
}
|
|
247
|
+
return buildResolvedSitemapUrls(effectiveSitemap, matchName, isChunked, resolvers, runtimeConfig, nitro);
|
|
248
|
+
}
|
|
249
|
+
export async function buildSitemapUrls(sitemap, resolvers, runtimeConfig, nitro) {
|
|
250
|
+
const { sitemaps, autoI18n, defaultSitemapsChunkSize } = runtimeConfig;
|
|
251
|
+
const chunkSize = defaultSitemapsChunkSize || void 0;
|
|
252
|
+
const chunkInfo = parseChunkInfo(sitemap.sitemapName, sitemaps, chunkSize);
|
|
253
|
+
if (autoI18n?.differentDomains) {
|
|
254
|
+
const domain = autoI18n.locales.find((e) => e.language === sitemap.sitemapName || e.code === sitemap.sitemapName)?.domain;
|
|
255
|
+
if (domain) {
|
|
256
|
+
const _tester = resolvers.canonicalUrlResolver;
|
|
257
|
+
resolvers.canonicalUrlResolver = (path) => resolveSitePath(path, {
|
|
258
|
+
absolute: true,
|
|
259
|
+
withBase: false,
|
|
260
|
+
siteUrl: withHttps(domain),
|
|
261
|
+
trailingSlash: _tester("/test/").endsWith("/"),
|
|
262
|
+
base: "/"
|
|
263
|
+
});
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
let effectiveSitemap = sitemap;
|
|
267
|
+
const baseSitemapName = chunkInfo.baseSitemapName;
|
|
268
|
+
if (chunkInfo.isChunked && baseSitemapName !== sitemap.sitemapName && sitemaps[baseSitemapName]) {
|
|
269
|
+
effectiveSitemap = sitemaps[baseSitemapName];
|
|
270
|
+
}
|
|
271
|
+
const matchName = chunkInfo.isChunked ? baseSitemapName : sitemap.sitemapName;
|
|
272
|
+
const resolved = await getResolvedSitemapUrls(effectiveSitemap, matchName, chunkInfo.isChunked, resolvers, runtimeConfig, nitro);
|
|
273
|
+
const urls = sliceUrlsForChunk(resolved.urls, sitemap.sitemapName, sitemaps, chunkSize);
|
|
274
|
+
return { urls, failedSources: resolved.failedSources };
|
|
275
|
+
}
|
|
276
|
+
export { urlsToXml } from "./xml.js";
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { createError, getRouterParam, sendRedirect } from "h3";
|
|
2
|
+
import { safeAppendHeader, safeSetHeader } from "../utils/h3-compat.js";
|
|
3
|
+
import { useNitroApp, useRuntimeConfig } from "nitropack/runtime";
|
|
4
|
+
import { joinURL, withBase, withLeadingSlash, withoutLeadingSlash, withoutTrailingSlash } from "ufo";
|
|
5
|
+
import { useSitemapRuntimeConfig } from "../utils.js";
|
|
6
|
+
import { buildSitemapIndex, urlsToIndexXml } from "./builder/sitemap-index.js";
|
|
7
|
+
import { createSitemap, useNitroUrlResolvers } from "./nitro.js";
|
|
8
|
+
import { getSitemapConfig, parseChunkInfo } from "./utils/chunk.js";
|
|
9
|
+
export async function sitemapXmlEventHandler(e) {
|
|
10
|
+
const runtimeConfig = useSitemapRuntimeConfig();
|
|
11
|
+
const { sitemaps } = runtimeConfig;
|
|
12
|
+
if ("index" in sitemaps)
|
|
13
|
+
return sendRedirect(e, withBase("/sitemap_index.xml", useRuntimeConfig().app.baseURL), import.meta.dev ? 302 : 301);
|
|
14
|
+
return createSitemap(e, Object.values(sitemaps)[0], runtimeConfig);
|
|
15
|
+
}
|
|
16
|
+
export async function sitemapIndexXmlEventHandler(e) {
|
|
17
|
+
const runtimeConfig = useSitemapRuntimeConfig();
|
|
18
|
+
const nitro = useNitroApp();
|
|
19
|
+
const resolvers = useNitroUrlResolvers(e);
|
|
20
|
+
const { entries: sitemaps, failedSources } = await buildSitemapIndex(resolvers, runtimeConfig, nitro);
|
|
21
|
+
if (import.meta.prerender) {
|
|
22
|
+
safeAppendHeader(
|
|
23
|
+
e,
|
|
24
|
+
"x-nitro-prerender",
|
|
25
|
+
sitemaps.filter((entry) => !!entry._sitemapName).map((entry) => encodeURIComponent(joinURL(runtimeConfig.sitemapsPathPrefix || "", `/${entry._sitemapName}.xml`))).join(", ")
|
|
26
|
+
);
|
|
27
|
+
}
|
|
28
|
+
const indexResolvedCtx = { sitemaps, event: e };
|
|
29
|
+
await nitro.hooks.callHook("sitemap:index-resolved", indexResolvedCtx);
|
|
30
|
+
const errorInfo = failedSources.length > 0 ? { messages: failedSources.map((f) => f.error), urls: failedSources.map((f) => f.url) } : void 0;
|
|
31
|
+
const output = urlsToIndexXml(indexResolvedCtx.sitemaps, resolvers, runtimeConfig, errorInfo);
|
|
32
|
+
const ctx = { sitemap: output, sitemapName: "sitemap", event: e };
|
|
33
|
+
await nitro.hooks.callHook("sitemap:output", ctx);
|
|
34
|
+
safeSetHeader(e, "Content-Type", "text/xml; charset=UTF-8");
|
|
35
|
+
if (runtimeConfig.cacheMaxAgeSeconds) {
|
|
36
|
+
safeSetHeader(e, "Cache-Control", `public, max-age=${runtimeConfig.cacheMaxAgeSeconds}, s-maxage=${runtimeConfig.cacheMaxAgeSeconds}, stale-while-revalidate=3600`);
|
|
37
|
+
const now = /* @__PURE__ */ new Date();
|
|
38
|
+
safeSetHeader(e, "X-Sitemap-Generated", now.toISOString());
|
|
39
|
+
safeSetHeader(e, "X-Sitemap-Cache-Duration", `${runtimeConfig.cacheMaxAgeSeconds}s`);
|
|
40
|
+
const expiryTime = new Date(now.getTime() + runtimeConfig.cacheMaxAgeSeconds * 1e3);
|
|
41
|
+
safeSetHeader(e, "X-Sitemap-Cache-Expires", expiryTime.toISOString());
|
|
42
|
+
const remainingSeconds = Math.floor((expiryTime.getTime() - now.getTime()) / 1e3);
|
|
43
|
+
safeSetHeader(e, "X-Sitemap-Cache-Remaining", `${remainingSeconds}s`);
|
|
44
|
+
} else {
|
|
45
|
+
safeSetHeader(e, "Cache-Control", `no-cache, no-store`);
|
|
46
|
+
}
|
|
47
|
+
return ctx.sitemap;
|
|
48
|
+
}
|
|
49
|
+
export async function sitemapChildXmlEventHandler(e) {
|
|
50
|
+
if (!e.path.endsWith(".xml"))
|
|
51
|
+
return;
|
|
52
|
+
const runtimeConfig = useSitemapRuntimeConfig(e);
|
|
53
|
+
const { sitemaps } = runtimeConfig;
|
|
54
|
+
let sitemapName = getRouterParam(e, "sitemap");
|
|
55
|
+
if (!sitemapName) {
|
|
56
|
+
const path = e.path;
|
|
57
|
+
const match = path.match(/(?:\/__sitemap__\/)?(.+)\.xml$/);
|
|
58
|
+
if (match)
|
|
59
|
+
sitemapName = match[1];
|
|
60
|
+
}
|
|
61
|
+
if (!sitemapName)
|
|
62
|
+
throw createError({ statusCode: 400, message: "Invalid sitemap request" });
|
|
63
|
+
sitemapName = sitemapName.replace(/\.xml$/, "");
|
|
64
|
+
sitemapName = withLeadingSlash(sitemapName);
|
|
65
|
+
if (sitemapName.startsWith("/__sitemap__/"))
|
|
66
|
+
sitemapName = sitemapName.replace("/__sitemap__/", "/");
|
|
67
|
+
if (runtimeConfig.sitemapsPathPrefix) {
|
|
68
|
+
const prefix = withLeadingSlash(runtimeConfig.sitemapsPathPrefix);
|
|
69
|
+
if (sitemapName.startsWith(prefix))
|
|
70
|
+
sitemapName = sitemapName.replace(prefix, "/");
|
|
71
|
+
}
|
|
72
|
+
sitemapName = withoutLeadingSlash(withoutTrailingSlash(sitemapName));
|
|
73
|
+
const chunkInfo = parseChunkInfo(sitemapName, sitemaps, runtimeConfig.defaultSitemapsChunkSize);
|
|
74
|
+
const isAutoChunked = typeof sitemaps.chunks !== "undefined" && !Number.isNaN(Number(sitemapName));
|
|
75
|
+
const sitemapExists = sitemapName in sitemaps || chunkInfo.baseSitemapName in sitemaps || isAutoChunked;
|
|
76
|
+
if (!sitemapExists)
|
|
77
|
+
throw createError({ statusCode: 404, message: `Sitemap "${sitemapName}" not found.` });
|
|
78
|
+
if (chunkInfo.isChunked && chunkInfo.chunkIndex !== void 0) {
|
|
79
|
+
const baseSitemap = sitemaps[chunkInfo.baseSitemapName];
|
|
80
|
+
if (baseSitemap && !baseSitemap.chunks && !baseSitemap._isChunking)
|
|
81
|
+
throw createError({ statusCode: 404, message: `Sitemap "${chunkInfo.baseSitemapName}" does not support chunking.` });
|
|
82
|
+
if (baseSitemap?._chunkCount !== void 0 && chunkInfo.chunkIndex >= baseSitemap._chunkCount)
|
|
83
|
+
throw createError({ statusCode: 404, message: `Chunk ${chunkInfo.chunkIndex} does not exist for sitemap "${chunkInfo.baseSitemapName}".` });
|
|
84
|
+
}
|
|
85
|
+
const sitemapConfig = getSitemapConfig(sitemapName, sitemaps, runtimeConfig.defaultSitemapsChunkSize || void 0);
|
|
86
|
+
return createSitemap(e, sitemapConfig, runtimeConfig);
|
|
87
|
+
}
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import { defu } from "defu";
|
|
2
|
+
import { createError } from "h3";
|
|
3
|
+
import { safeGetHeader, safeGetQuery, safeSetHeader } from "../utils/h3-compat.js";
|
|
4
|
+
import { defineCachedFunction, useNitroApp } from "nitropack/runtime";
|
|
5
|
+
import { fixSlashes } from "nuxt-site-config/urls";
|
|
6
|
+
import { getPathRobotConfig } from "#internal/nuxt-robots/getPathRobotConfig";
|
|
7
|
+
import { getSiteConfig } from "#site-config/server/composables/getSiteConfig";
|
|
8
|
+
import { createSitePathResolver } from "#site-config/server/composables/utils";
|
|
9
|
+
import staticConfig from "#sitemap-virtual/static-config.mjs";
|
|
10
|
+
import { logger, mergeOnKey, splitForLocales } from "../../utils-pure.js";
|
|
11
|
+
import { createNitroRouteRuleMatcher } from "../kit.js";
|
|
12
|
+
import { buildSitemapUrls, urlsToXml } from "./builder/sitemap.js";
|
|
13
|
+
import { normaliseEntry, preNormalizeEntry } from "./urlset/normalise.js";
|
|
14
|
+
import { sortInPlace } from "./urlset/sort.js";
|
|
15
|
+
const SERVER_CACHE_MAX_AGE = staticConfig.cacheMaxAgeSeconds || 60 * 10;
|
|
16
|
+
|
|
17
|
+
export function useNitroUrlResolvers(e) {
|
|
18
|
+
const canonicalQuery = safeGetQuery(e).canonical;
|
|
19
|
+
const isShowingCanonical = typeof canonicalQuery !== "undefined" && canonicalQuery !== "false";
|
|
20
|
+
const siteConfig = getSiteConfig(e);
|
|
21
|
+
return {
|
|
22
|
+
event: e,
|
|
23
|
+
fixSlashes: (path) => fixSlashes(siteConfig.trailingSlash, path),
|
|
24
|
+
// we need these as they depend on the nitro event
|
|
25
|
+
canonicalUrlResolver: createSitePathResolver(e, {
|
|
26
|
+
canonical: isShowingCanonical || !import.meta.dev,
|
|
27
|
+
absolute: true,
|
|
28
|
+
withBase: true
|
|
29
|
+
}),
|
|
30
|
+
relativeBaseUrlResolver: createSitePathResolver(e, { absolute: false, withBase: true })
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
async function buildSitemapXml(event, definition, resolvers, runtimeConfig) {
|
|
34
|
+
const { sitemapName } = definition;
|
|
35
|
+
const nitro = useNitroApp();
|
|
36
|
+
if (import.meta.prerender) {
|
|
37
|
+
const config = getSiteConfig(event);
|
|
38
|
+
if (!config.url && !nitro._sitemapWarned) {
|
|
39
|
+
nitro._sitemapWarned = true;
|
|
40
|
+
logger.error("Sitemap Site URL missing!");
|
|
41
|
+
logger.info("To fix this please add `{ site: { url: 'site.com' } }` to your Nuxt config or a `NUXT_PUBLIC_SITE_URL=site.com` to your .env. Learn more at https://nuxtseo.com/site-config/getting-started/how-it-works");
|
|
42
|
+
throw createError({
|
|
43
|
+
statusMessage: "You must provide a site URL to prerender a sitemap.",
|
|
44
|
+
statusCode: 500
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
const { urls: sitemapUrls, failedSources } = await buildSitemapUrls(definition, resolvers, runtimeConfig, nitro);
|
|
49
|
+
if (import.meta.prerender && failedSources.length) {
|
|
50
|
+
throw createError({
|
|
51
|
+
statusCode: 500,
|
|
52
|
+
message: `Sitemap generation failed due to ${failedSources.length} failed sources: ${failedSources.map((s) => `"${s.url}" (${s.error})`).join(", ")}`
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
const routeRuleMatcher = createNitroRouteRuleMatcher();
|
|
56
|
+
const { autoI18n } = runtimeConfig;
|
|
57
|
+
let validCount = 0;
|
|
58
|
+
for (let i = 0; i < sitemapUrls.length; i++) {
|
|
59
|
+
const u = sitemapUrls[i];
|
|
60
|
+
const path = u._path?.pathname || u.loc;
|
|
61
|
+
if (!getPathRobotConfig(event, { path, skipSiteIndexable: true }).indexable)
|
|
62
|
+
continue;
|
|
63
|
+
let routeRules = routeRuleMatcher(path);
|
|
64
|
+
if (autoI18n?.locales && autoI18n?.strategy !== "no_prefix") {
|
|
65
|
+
const match = splitForLocales(path, autoI18n.locales.map((l) => l.code));
|
|
66
|
+
const pathWithoutPrefix = match[1];
|
|
67
|
+
if (pathWithoutPrefix && pathWithoutPrefix !== path)
|
|
68
|
+
routeRules = defu(routeRules, routeRuleMatcher(pathWithoutPrefix));
|
|
69
|
+
}
|
|
70
|
+
if (routeRules.sitemap === false)
|
|
71
|
+
continue;
|
|
72
|
+
if (typeof routeRules.robots !== "undefined" && !routeRules.robots)
|
|
73
|
+
continue;
|
|
74
|
+
const hasRobotsDisabled = Object.entries(routeRules.headers || {}).some(([name, value]) => name.toLowerCase() === "x-robots-tag" && value.toLowerCase().includes("noindex"));
|
|
75
|
+
if (routeRules.redirect || hasRobotsDisabled)
|
|
76
|
+
continue;
|
|
77
|
+
sitemapUrls[validCount++] = routeRules.sitemap ? defu(u, routeRules.sitemap) : u;
|
|
78
|
+
}
|
|
79
|
+
sitemapUrls.length = validCount;
|
|
80
|
+
if (import.meta.dev && validCount === 0 && sitemapUrls.length > 0) {
|
|
81
|
+
logger.warn(`Sitemap had ${sitemapUrls.length} that were all filtered out. This may be due to a robots rules blocking these URLs from indexing. Check your /** route rules or robots.txt configuration.`);
|
|
82
|
+
}
|
|
83
|
+
const locSize = sitemapUrls.length;
|
|
84
|
+
const resolvedCtx = {
|
|
85
|
+
urls: sitemapUrls,
|
|
86
|
+
sitemapName,
|
|
87
|
+
event
|
|
88
|
+
};
|
|
89
|
+
await nitro.hooks.callHook("sitemap:resolved", resolvedCtx);
|
|
90
|
+
if (resolvedCtx.urls.length !== locSize) {
|
|
91
|
+
resolvedCtx.urls = resolvedCtx.urls.map((e) => preNormalizeEntry(e, resolvers));
|
|
92
|
+
}
|
|
93
|
+
const maybeSort = (urls2) => runtimeConfig.sortEntries ? sortInPlace(urls2) : urls2;
|
|
94
|
+
const defaults = definition.defaults || {};
|
|
95
|
+
const normalizedPreDedupe = resolvedCtx.urls.map((e) => normaliseEntry(e, defaults, resolvers));
|
|
96
|
+
const urls = maybeSort(mergeOnKey(normalizedPreDedupe, "_key").map((e) => normaliseEntry(e, defaults, resolvers)));
|
|
97
|
+
if (definition._isChunking && definition.sitemapName.includes("-")) {
|
|
98
|
+
const parts = definition.sitemapName.split("-");
|
|
99
|
+
const lastPart = parts.pop();
|
|
100
|
+
if (!Number.isNaN(Number(lastPart))) {
|
|
101
|
+
const chunkIndex = Number(lastPart);
|
|
102
|
+
const baseSitemapName = parts.join("-");
|
|
103
|
+
if (urls.length === 0 && chunkIndex > 0) {
|
|
104
|
+
throw createError({
|
|
105
|
+
statusCode: 404,
|
|
106
|
+
message: `Sitemap chunk ${chunkIndex} for "${baseSitemapName}" does not exist.`
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
const errorInfo = failedSources.length > 0 ? {
|
|
112
|
+
messages: failedSources.map((f) => f.error),
|
|
113
|
+
urls: failedSources.map((f) => f.url)
|
|
114
|
+
} : void 0;
|
|
115
|
+
const sitemap = urlsToXml(urls, resolvers, runtimeConfig, errorInfo);
|
|
116
|
+
const ctx = { sitemap, sitemapName, event };
|
|
117
|
+
await nitro.hooks.callHook("sitemap:output", ctx);
|
|
118
|
+
return ctx.sitemap;
|
|
119
|
+
}
|
|
120
|
+
const buildSitemapXmlCached = defineCachedFunction(
|
|
121
|
+
buildSitemapXml,
|
|
122
|
+
{
|
|
123
|
+
name: "sitemap:xml",
|
|
124
|
+
group: "sitemap",
|
|
125
|
+
maxAge: SERVER_CACHE_MAX_AGE,
|
|
126
|
+
base: "sitemap",
|
|
127
|
+
// Use the sitemap storage
|
|
128
|
+
getKey: (event, definition) => {
|
|
129
|
+
const host = safeGetHeader(event, "host") || safeGetHeader(event, "x-forwarded-host") || "";
|
|
130
|
+
const proto = safeGetHeader(event, "x-forwarded-proto") || "https";
|
|
131
|
+
const sitemapName = definition.sitemapName || "default";
|
|
132
|
+
return `${sitemapName}-${proto}-${host}`;
|
|
133
|
+
},
|
|
134
|
+
swr: true
|
|
135
|
+
// Enable stale-while-revalidate
|
|
136
|
+
}
|
|
137
|
+
);
|
|
138
|
+
export async function createSitemap(event, definition, runtimeConfig) {
|
|
139
|
+
const resolvers = useNitroUrlResolvers(event);
|
|
140
|
+
const shouldCache = !import.meta.dev && !import.meta.prerender && typeof runtimeConfig.cacheMaxAgeSeconds === "number" && runtimeConfig.cacheMaxAgeSeconds > 0;
|
|
141
|
+
const xml = shouldCache ? await buildSitemapXmlCached(event, definition, resolvers, runtimeConfig) : await buildSitemapXml(event, definition, resolvers, runtimeConfig);
|
|
142
|
+
safeSetHeader(event, "Content-Type", "text/xml; charset=UTF-8");
|
|
143
|
+
if (runtimeConfig.cacheMaxAgeSeconds) {
|
|
144
|
+
safeSetHeader(event, "Cache-Control", `public, max-age=${runtimeConfig.cacheMaxAgeSeconds}, s-maxage=${runtimeConfig.cacheMaxAgeSeconds}, stale-while-revalidate=3600`);
|
|
145
|
+
const now = /* @__PURE__ */ new Date();
|
|
146
|
+
safeSetHeader(event, "X-Sitemap-Generated", now.toISOString());
|
|
147
|
+
safeSetHeader(event, "X-Sitemap-Cache-Duration", `${runtimeConfig.cacheMaxAgeSeconds}s`);
|
|
148
|
+
const expiryTime = new Date(now.getTime() + runtimeConfig.cacheMaxAgeSeconds * 1e3);
|
|
149
|
+
safeSetHeader(event, "X-Sitemap-Cache-Expires", expiryTime.toISOString());
|
|
150
|
+
const remainingSeconds = Math.floor((expiryTime.getTime() - now.getTime()) / 1e3);
|
|
151
|
+
safeSetHeader(event, "X-Sitemap-Cache-Remaining", `${remainingSeconds}s`);
|
|
152
|
+
} else {
|
|
153
|
+
safeSetHeader(event, "Cache-Control", `no-cache, no-store`);
|
|
154
|
+
}
|
|
155
|
+
event.context._isSitemap = true;
|
|
156
|
+
return xml;
|
|
157
|
+
}
|