@onruntime/next-sitemap 0.6.2 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +12 -0
- package/dist/app/index.cjs +200 -213
- package/dist/app/index.d.cts +11 -11
- package/dist/app/index.d.ts +11 -11
- package/dist/app/index.js +201 -210
- package/dist/index.cjs +214 -4
- package/dist/index.d.cts +65 -2
- package/dist/index.d.ts +65 -2
- package/dist/index.js +186 -5
- package/dist/loader.js +38 -0
- package/dist/pages/index.cjs +206 -205
- package/dist/pages/index.d.cts +24 -30
- package/dist/pages/index.d.ts +24 -30
- package/dist/pages/index.js +207 -202
- package/dist/worker.cjs +267 -0
- package/package.json +2 -2
package/dist/pages/index.d.ts
CHANGED
|
@@ -2,6 +2,7 @@ import { MetadataRoute, NextApiRequest, NextApiResponse } from 'next';
|
|
|
2
2
|
export { MetadataRoute } from 'next';
|
|
3
3
|
|
|
4
4
|
type ChangeFrequency = "always" | "hourly" | "daily" | "weekly" | "monthly" | "yearly" | "never";
|
|
5
|
+
|
|
5
6
|
interface SitemapConfig {
|
|
6
7
|
/**
|
|
7
8
|
* Base URL of the site (e.g., "https://example.com")
|
|
@@ -54,6 +55,16 @@ interface SitemapConfig {
|
|
|
54
55
|
* @example additionalSitemaps: ["/products-sitemap.xml", "/blog-sitemap.xml"]
|
|
55
56
|
*/
|
|
56
57
|
additionalSitemaps?: string[];
|
|
58
|
+
/**
|
|
59
|
+
* Enable debug logging to diagnose issues with route discovery
|
|
60
|
+
* @default false
|
|
61
|
+
*/
|
|
62
|
+
debug?: boolean;
|
|
63
|
+
/**
|
|
64
|
+
* Include "Powered by @onruntime/next-sitemap" comment in generated XML
|
|
65
|
+
* @default true
|
|
66
|
+
*/
|
|
67
|
+
poweredBy?: boolean;
|
|
57
68
|
}
|
|
58
69
|
interface SitemapEntry {
|
|
59
70
|
url: string;
|
|
@@ -65,39 +76,35 @@ interface SitemapEntry {
|
|
|
65
76
|
};
|
|
66
77
|
}
|
|
67
78
|
|
|
79
|
+
interface RobotsConfig extends MetadataRoute.Robots {
|
|
80
|
+
/**
|
|
81
|
+
* Include "Powered by @onruntime/next-sitemap" comment
|
|
82
|
+
* @default true
|
|
83
|
+
*/
|
|
84
|
+
poweredBy?: boolean;
|
|
85
|
+
}
|
|
68
86
|
/**
|
|
69
87
|
* Generate robots.txt content from configuration
|
|
70
88
|
* Compatible with Next.js MetadataRoute.Robots
|
|
71
89
|
*/
|
|
72
|
-
declare function generateRobotsTxt(config:
|
|
90
|
+
declare function generateRobotsTxt(config: RobotsConfig): string;
|
|
73
91
|
|
|
74
92
|
interface CreateSitemapApiHandlerOptions extends SitemapConfig {
|
|
75
93
|
/**
|
|
76
94
|
* Path to the pages directory to scan for page files.
|
|
77
95
|
* Can be absolute or relative to process.cwd().
|
|
78
96
|
* If not provided, auto-detects src/pages or pages.
|
|
79
|
-
*
|
|
80
|
-
* Example:
|
|
81
|
-
* ```ts
|
|
82
|
-
* pagesDirectory: 'src/pages'
|
|
83
|
-
* ```
|
|
84
97
|
*/
|
|
85
98
|
pagesDirectory?: string;
|
|
86
|
-
/**
|
|
87
|
-
* Enable debug logging to diagnose issues with route discovery
|
|
88
|
-
* Logs info about getStaticPaths calls and skipped routes
|
|
89
|
-
* @default false
|
|
90
|
-
*/
|
|
91
|
-
debug?: boolean;
|
|
92
99
|
}
|
|
93
100
|
/**
|
|
94
101
|
* Create API handler for sitemap index route
|
|
95
|
-
* Use in: pages/api/sitemap.xml.ts
|
|
102
|
+
* Use in: pages/api/sitemap.xml.ts
|
|
96
103
|
*/
|
|
97
104
|
declare function createSitemapIndexApiHandler(options: CreateSitemapApiHandlerOptions): (_req: NextApiRequest, res: NextApiResponse) => Promise<void>;
|
|
98
105
|
/**
|
|
99
106
|
* Create API handler for individual sitemap routes
|
|
100
|
-
* Use in: pages/api/sitemap/[id].ts
|
|
107
|
+
* Use in: pages/api/sitemap/[id].ts
|
|
101
108
|
*/
|
|
102
109
|
declare function createSitemapApiHandler(options: CreateSitemapApiHandlerOptions): (req: NextApiRequest, res: NextApiResponse) => Promise<void>;
|
|
103
110
|
/**
|
|
@@ -114,21 +121,8 @@ declare function getSitemapStaticPaths(options: CreateSitemapApiHandlerOptions):
|
|
|
114
121
|
}>;
|
|
115
122
|
/**
|
|
116
123
|
* Create API handler for robots.txt
|
|
117
|
-
* Use in: pages/api/robots.txt.ts
|
|
118
|
-
*
|
|
119
|
-
* @example
|
|
120
|
-
* // pages/api/robots.txt.ts
|
|
121
|
-
* import { createRobotsApiHandler } from "@onruntime/next-sitemap/pages";
|
|
122
|
-
*
|
|
123
|
-
* export default createRobotsApiHandler({
|
|
124
|
-
* rules: {
|
|
125
|
-
* userAgent: "*",
|
|
126
|
-
* allow: "/",
|
|
127
|
-
* disallow: ["/admin", "/private"],
|
|
128
|
-
* },
|
|
129
|
-
* sitemap: "https://example.com/sitemap.xml",
|
|
130
|
-
* });
|
|
124
|
+
* Use in: pages/api/robots.txt.ts
|
|
131
125
|
*/
|
|
132
|
-
declare function createRobotsApiHandler(config:
|
|
126
|
+
declare function createRobotsApiHandler(config: RobotsConfig): (_req: NextApiRequest, res: NextApiResponse) => void;
|
|
133
127
|
|
|
134
|
-
export { type ChangeFrequency, type CreateSitemapApiHandlerOptions, type SitemapConfig, type SitemapEntry, createRobotsApiHandler, createSitemapApiHandler, createSitemapIndexApiHandler, generateRobotsTxt, getSitemapStaticPaths };
|
|
128
|
+
export { type ChangeFrequency, type CreateSitemapApiHandlerOptions, type RobotsConfig, type SitemapConfig, type SitemapEntry, createRobotsApiHandler, createSitemapApiHandler, createSitemapIndexApiHandler, generateRobotsTxt, getSitemapStaticPaths };
|
package/dist/pages/index.js
CHANGED
|
@@ -1,9 +1,168 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
3
|
-
import
|
|
4
|
-
import
|
|
1
|
+
import { existsSync, readdirSync } from 'fs';
|
|
2
|
+
import { dirname, join, isAbsolute, relative, delimiter } from 'path';
|
|
3
|
+
import * as childProcess from 'child_process';
|
|
4
|
+
import { fileURLToPath } from 'url';
|
|
5
5
|
|
|
6
6
|
// src/pages/index.ts
|
|
7
|
+
var NO_STATIC_PARAMS = "NO_STATIC_PARAMS";
|
|
8
|
+
var spawnProcess = childProcess.spawn;
|
|
9
|
+
var __dirname$1 = dirname(fileURLToPath(import.meta.url));
|
|
10
|
+
var paramsCache = /* @__PURE__ */ new Map();
|
|
11
|
+
var isDev = process.env.NODE_ENV === "development";
|
|
12
|
+
async function executeWorker(directory, fileKey, debug) {
|
|
13
|
+
const absolutePath = join(directory, fileKey.replace("./", ""));
|
|
14
|
+
const projectRoot = process.cwd();
|
|
15
|
+
const distRoot = join(__dirname$1, "..");
|
|
16
|
+
const workerPath = join(distRoot, "worker.cjs");
|
|
17
|
+
const loaderPath = join(distRoot, "loader.js");
|
|
18
|
+
if (debug) {
|
|
19
|
+
console.log(`[next-sitemap] Worker path: ${workerPath}`);
|
|
20
|
+
console.log(`[next-sitemap] Worker exists: ${existsSync(workerPath)}`);
|
|
21
|
+
console.log(`[next-sitemap] Loader path: ${loaderPath}`);
|
|
22
|
+
console.log(`[next-sitemap] Loader exists: ${existsSync(loaderPath)}`);
|
|
23
|
+
}
|
|
24
|
+
return new Promise((resolve) => {
|
|
25
|
+
const nodePath = [
|
|
26
|
+
join(projectRoot, "node_modules"),
|
|
27
|
+
join(__dirname$1, "..", "node_modules")
|
|
28
|
+
].join(delimiter);
|
|
29
|
+
const importFlag = ["--import", loaderPath];
|
|
30
|
+
const args = [...importFlag, workerPath, absolutePath, projectRoot];
|
|
31
|
+
const child = spawnProcess("node", args, {
|
|
32
|
+
cwd: projectRoot,
|
|
33
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
34
|
+
env: { ...process.env, NODE_PATH: nodePath }
|
|
35
|
+
});
|
|
36
|
+
let stdout = "";
|
|
37
|
+
let stderr = "";
|
|
38
|
+
child.stdout.on("data", (data) => {
|
|
39
|
+
stdout += data.toString();
|
|
40
|
+
});
|
|
41
|
+
child.stderr.on("data", (data) => {
|
|
42
|
+
stderr += data.toString();
|
|
43
|
+
});
|
|
44
|
+
child.on("close", (code) => {
|
|
45
|
+
if (debug && stderr) {
|
|
46
|
+
console.warn(`[next-sitemap] Worker stderr: ${stderr}`);
|
|
47
|
+
}
|
|
48
|
+
if (code !== 0 && code !== null) {
|
|
49
|
+
if (debug) console.warn(`[next-sitemap] Worker exited with code ${code}`);
|
|
50
|
+
resolve(null);
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
try {
|
|
54
|
+
const lines = stdout.trim().split("\n");
|
|
55
|
+
const result = JSON.parse(lines[lines.length - 1]);
|
|
56
|
+
if (result.success) {
|
|
57
|
+
resolve(result.params);
|
|
58
|
+
} else {
|
|
59
|
+
if (result.error !== NO_STATIC_PARAMS && debug) {
|
|
60
|
+
console.warn(`[next-sitemap] Worker error: ${result.error}`);
|
|
61
|
+
}
|
|
62
|
+
resolve(null);
|
|
63
|
+
}
|
|
64
|
+
} catch {
|
|
65
|
+
if (debug) console.warn(`[next-sitemap] Failed to parse worker output: ${stdout}`);
|
|
66
|
+
resolve(null);
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
child.on("error", (err) => {
|
|
70
|
+
if (debug) console.warn(`[next-sitemap] Failed to spawn worker: ${err.message}`);
|
|
71
|
+
resolve(null);
|
|
72
|
+
});
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
async function getRouteParams(route, directory, debug) {
|
|
76
|
+
const cacheKey = route.fileKey;
|
|
77
|
+
if (paramsCache.has(cacheKey)) {
|
|
78
|
+
return paramsCache.get(cacheKey);
|
|
79
|
+
}
|
|
80
|
+
if (debug) {
|
|
81
|
+
console.log(`[next-sitemap] ${route.pathname}: executing static params via worker`);
|
|
82
|
+
}
|
|
83
|
+
const params = await executeWorker(directory, route.fileKey, debug);
|
|
84
|
+
paramsCache.set(cacheKey, params);
|
|
85
|
+
if (debug && params) {
|
|
86
|
+
console.log(`[next-sitemap] ${route.pathname}: got ${params.length} params`);
|
|
87
|
+
}
|
|
88
|
+
return params;
|
|
89
|
+
}
|
|
90
|
+
async function generateAllPaths(routes, directory, debug) {
|
|
91
|
+
const staticPaths = ["/"];
|
|
92
|
+
const dynamicRoutes = [];
|
|
93
|
+
for (const route of routes) {
|
|
94
|
+
if (route.dynamicSegments.length === 0) {
|
|
95
|
+
if (route.pathname !== "/") {
|
|
96
|
+
staticPaths.push(route.pathname);
|
|
97
|
+
}
|
|
98
|
+
} else {
|
|
99
|
+
dynamicRoutes.push(route);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
const startTime = isDev ? Date.now() : 0;
|
|
103
|
+
if (isDev && dynamicRoutes.length > 0) {
|
|
104
|
+
console.log(`[next-sitemap] Generating sitemap for ${dynamicRoutes.length} dynamic routes (dev only, instant in production)...`);
|
|
105
|
+
}
|
|
106
|
+
const dynamicResults = await Promise.all(
|
|
107
|
+
dynamicRoutes.map(async (route) => {
|
|
108
|
+
const params = await getRouteParams(route, directory, debug);
|
|
109
|
+
if (!params || params.length === 0) {
|
|
110
|
+
if (debug) {
|
|
111
|
+
console.warn(`[next-sitemap] Skipping dynamic route ${route.pathname}: no static params or empty result.`);
|
|
112
|
+
}
|
|
113
|
+
return [];
|
|
114
|
+
}
|
|
115
|
+
const paths = [];
|
|
116
|
+
for (const param of params) {
|
|
117
|
+
let path = route.pathname;
|
|
118
|
+
let valid = true;
|
|
119
|
+
for (const segment of route.dynamicSegments) {
|
|
120
|
+
const value = param[segment];
|
|
121
|
+
if (value === void 0) {
|
|
122
|
+
if (debug) {
|
|
123
|
+
console.warn(`[next-sitemap] ${route.pathname}: missing param "${segment}" in`, param);
|
|
124
|
+
}
|
|
125
|
+
valid = false;
|
|
126
|
+
break;
|
|
127
|
+
}
|
|
128
|
+
path = path.replace(`[${segment}]`, value);
|
|
129
|
+
}
|
|
130
|
+
if (valid) paths.push(path);
|
|
131
|
+
}
|
|
132
|
+
return paths;
|
|
133
|
+
})
|
|
134
|
+
);
|
|
135
|
+
const allPaths = new Set(staticPaths);
|
|
136
|
+
for (const paths of dynamicResults) {
|
|
137
|
+
for (const path of paths) {
|
|
138
|
+
allPaths.add(path);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
if (isDev && dynamicRoutes.length > 0) {
|
|
142
|
+
const elapsed = Date.now() - startTime;
|
|
143
|
+
console.log(`[next-sitemap] Done! Found ${allPaths.size} total URLs in ${elapsed}ms.`);
|
|
144
|
+
}
|
|
145
|
+
return Array.from(allPaths);
|
|
146
|
+
}
|
|
147
|
+
function pathsToEntries(paths, config) {
|
|
148
|
+
const { baseUrl, locales = [], defaultLocale, exclude, priority, changeFreq } = config;
|
|
149
|
+
return paths.filter((pathname) => !shouldExclude(pathname, exclude)).map((pathname) => {
|
|
150
|
+
const entry = {
|
|
151
|
+
url: buildUrl(baseUrl, pathname, defaultLocale, defaultLocale),
|
|
152
|
+
lastModified: /* @__PURE__ */ new Date(),
|
|
153
|
+
changeFrequency: getChangeFreq(pathname, changeFreq),
|
|
154
|
+
priority: getPriority(pathname, priority)
|
|
155
|
+
};
|
|
156
|
+
if (locales.length > 0) {
|
|
157
|
+
entry.alternates = {
|
|
158
|
+
languages: Object.fromEntries(
|
|
159
|
+
locales.map((locale) => [locale, buildUrl(baseUrl, pathname, locale, defaultLocale)])
|
|
160
|
+
)
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
return entry;
|
|
164
|
+
});
|
|
165
|
+
}
|
|
7
166
|
|
|
8
167
|
// src/index.ts
|
|
9
168
|
function calculateDepthPriority(pathname) {
|
|
@@ -52,7 +211,8 @@ function buildUrl(baseUrl, pathname, locale, defaultLocale) {
|
|
|
52
211
|
}
|
|
53
212
|
return `${baseUrl}/${locale}${normalizedPath}`;
|
|
54
213
|
}
|
|
55
|
-
function generateSitemapXml(entries) {
|
|
214
|
+
function generateSitemapXml(entries, options) {
|
|
215
|
+
const { poweredBy = true } = options || {};
|
|
56
216
|
const hasAlternates = entries.some((e) => e.alternates?.languages);
|
|
57
217
|
const xmlns = hasAlternates ? 'xmlns="http://www.sitemaps.org/schemas/sitemap/0.9" xmlns:xhtml="http://www.w3.org/1999/xhtml"' : 'xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"';
|
|
58
218
|
const urlEntries = entries.map((entry) => {
|
|
@@ -76,13 +236,14 @@ function generateSitemapXml(entries) {
|
|
|
76
236
|
${parts.join("\n")}
|
|
77
237
|
</url>`;
|
|
78
238
|
}).join("\n");
|
|
79
|
-
|
|
239
|
+
const comment = poweredBy ? "\n<!-- Powered by @onruntime/next-sitemap -->" : "";
|
|
240
|
+
return `<?xml version="1.0" encoding="UTF-8"?>${comment}
|
|
80
241
|
<urlset ${xmlns}>
|
|
81
242
|
${urlEntries}
|
|
82
243
|
</urlset>`;
|
|
83
244
|
}
|
|
84
245
|
function generateSitemapIndexXml(baseUrl, sitemapCount, options) {
|
|
85
|
-
const { sitemapPattern = "/sitemap-{id}.xml", additionalSitemaps = [] } = options || {};
|
|
246
|
+
const { sitemapPattern = "/sitemap-{id}.xml", additionalSitemaps = [], poweredBy = true } = options || {};
|
|
86
247
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
87
248
|
const paginatedEntries = Array.from({ length: sitemapCount }, (_, i) => {
|
|
88
249
|
const loc = `${baseUrl}${sitemapPattern.replace("{id}", String(i))}`;
|
|
@@ -99,7 +260,8 @@ function generateSitemapIndexXml(baseUrl, sitemapCount, options) {
|
|
|
99
260
|
</sitemap>`;
|
|
100
261
|
});
|
|
101
262
|
const allEntries = [...paginatedEntries, ...additionalEntries].join("\n");
|
|
102
|
-
|
|
263
|
+
const comment = poweredBy ? "\n<!-- Powered by @onruntime/next-sitemap -->" : "";
|
|
264
|
+
return `<?xml version="1.0" encoding="UTF-8"?>${comment}
|
|
103
265
|
<sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
|
|
104
266
|
${allEntries}
|
|
105
267
|
</sitemapindex>`;
|
|
@@ -107,7 +269,12 @@ ${allEntries}
|
|
|
107
269
|
|
|
108
270
|
// src/pages/robots.ts
|
|
109
271
|
function generateRobotsTxt(config) {
|
|
272
|
+
const { poweredBy = true } = config;
|
|
110
273
|
const lines = [];
|
|
274
|
+
if (poweredBy) {
|
|
275
|
+
lines.push("# Powered by @onruntime/next-sitemap");
|
|
276
|
+
lines.push("");
|
|
277
|
+
}
|
|
111
278
|
const rules = Array.isArray(config.rules) ? config.rules : [config.rules];
|
|
112
279
|
for (const rule of rules) {
|
|
113
280
|
const userAgents = Array.isArray(rule.userAgent) ? rule.userAgent : [rule.userAgent];
|
|
@@ -144,18 +311,17 @@ function generateRobotsTxt(config) {
|
|
|
144
311
|
}
|
|
145
312
|
|
|
146
313
|
// src/pages/index.ts
|
|
147
|
-
var joinPath = (...segments) => path.join(...segments);
|
|
148
314
|
function findPageFiles(dir, baseDir = dir) {
|
|
149
315
|
const files = [];
|
|
150
316
|
try {
|
|
151
|
-
const entries =
|
|
317
|
+
const entries = readdirSync(dir, { withFileTypes: true });
|
|
152
318
|
for (const entry of entries) {
|
|
153
|
-
const fullPath =
|
|
319
|
+
const fullPath = join(dir, entry.name);
|
|
154
320
|
if (entry.isDirectory()) {
|
|
155
321
|
if (entry.name === "api") continue;
|
|
156
322
|
files.push(...findPageFiles(fullPath, baseDir));
|
|
157
323
|
} else if (/\.(tsx?|jsx?)$/.test(entry.name) && !entry.name.startsWith("_")) {
|
|
158
|
-
const relativePath = "./" +
|
|
324
|
+
const relativePath = "./" + relative(baseDir, fullPath).replace(/\\/g, "/");
|
|
159
325
|
files.push(relativePath);
|
|
160
326
|
}
|
|
161
327
|
}
|
|
@@ -163,67 +329,12 @@ function findPageFiles(dir, baseDir = dir) {
|
|
|
163
329
|
}
|
|
164
330
|
return files;
|
|
165
331
|
}
|
|
166
|
-
function
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
return srcPages;
|
|
170
|
-
}
|
|
171
|
-
return path.join(process.cwd(), "pages");
|
|
172
|
-
}
|
|
173
|
-
function resolvePagesDirectory(options) {
|
|
174
|
-
if (options.pagesDirectory) {
|
|
175
|
-
return path.isAbsolute(options.pagesDirectory) ? options.pagesDirectory : path.join(process.cwd(), options.pagesDirectory);
|
|
332
|
+
function resolvePagesDirectory(pagesDirectory) {
|
|
333
|
+
if (pagesDirectory) {
|
|
334
|
+
return isAbsolute(pagesDirectory) ? pagesDirectory : join(process.cwd(), pagesDirectory);
|
|
176
335
|
}
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
function getPageKeys(options) {
|
|
180
|
-
return findPageFiles(resolvePagesDirectory(options));
|
|
181
|
-
}
|
|
182
|
-
var jitiCache = /* @__PURE__ */ new Map();
|
|
183
|
-
function getTsconfigPaths(projectRoot) {
|
|
184
|
-
const alias = {};
|
|
185
|
-
try {
|
|
186
|
-
const tsconfigPath = path.join(projectRoot, "tsconfig.json");
|
|
187
|
-
if (fs.existsSync(tsconfigPath)) {
|
|
188
|
-
const content = fs.readFileSync(tsconfigPath, "utf-8");
|
|
189
|
-
const withoutComments = stripJsonComments(content);
|
|
190
|
-
const cleaned = withoutComments.replace(/,(\s*[}\]])/g, "$1");
|
|
191
|
-
const tsconfig = JSON.parse(cleaned);
|
|
192
|
-
const baseUrl = tsconfig.compilerOptions?.baseUrl || ".";
|
|
193
|
-
const paths = tsconfig.compilerOptions?.paths || {};
|
|
194
|
-
for (const [key, values] of Object.entries(paths)) {
|
|
195
|
-
if (values.length > 0) {
|
|
196
|
-
const aliasKey = key.replace(/\*$/, "");
|
|
197
|
-
const aliasValue = joinPath(projectRoot, baseUrl, values[0].replace(/\*$/, ""));
|
|
198
|
-
alias[aliasKey] = aliasValue;
|
|
199
|
-
}
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
} catch {
|
|
203
|
-
}
|
|
204
|
-
return alias;
|
|
205
|
-
}
|
|
206
|
-
function getJiti(projectRoot) {
|
|
207
|
-
if (jitiCache.has(projectRoot)) {
|
|
208
|
-
return jitiCache.get(projectRoot);
|
|
209
|
-
}
|
|
210
|
-
const alias = getTsconfigPaths(projectRoot);
|
|
211
|
-
const jiti = createJiti(import.meta.url, {
|
|
212
|
-
moduleCache: false,
|
|
213
|
-
interopDefault: true,
|
|
214
|
-
jsx: true,
|
|
215
|
-
alias
|
|
216
|
-
});
|
|
217
|
-
jitiCache.set(projectRoot, jiti);
|
|
218
|
-
return jiti;
|
|
219
|
-
}
|
|
220
|
-
async function importPage(pagesDirectory, key) {
|
|
221
|
-
const relativePath = key.replace("./", "");
|
|
222
|
-
const absolutePath = path.join(pagesDirectory, relativePath);
|
|
223
|
-
const projectRoot = process.cwd();
|
|
224
|
-
const jiti = getJiti(projectRoot);
|
|
225
|
-
const module = await jiti.import(absolutePath);
|
|
226
|
-
return module.default || module;
|
|
336
|
+
const srcPages = join(process.cwd(), "src/pages");
|
|
337
|
+
return existsSync(srcPages) ? srcPages : join(process.cwd(), "pages");
|
|
227
338
|
}
|
|
228
339
|
function extractRoutes(pageKeys, localeSegment) {
|
|
229
340
|
const routes = [];
|
|
@@ -232,10 +343,8 @@ function extractRoutes(pageKeys, localeSegment) {
|
|
|
232
343
|
let pathname = key.replace(/^\.\//, "/").replace(/\.(tsx?|jsx?)$/, "").replace(/\/index$/, "/");
|
|
233
344
|
if (pathname === "") pathname = "/";
|
|
234
345
|
if (localeSegment) {
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
""
|
|
238
|
-
);
|
|
346
|
+
const escapedSegment = localeSegment.replace(/[[\]]/g, "\\$&");
|
|
347
|
+
pathname = pathname.replace(new RegExp(`^/${escapedSegment}`), "");
|
|
239
348
|
}
|
|
240
349
|
if (/(?:^|\/)(src|pages)(?:\/|$)/.test(pathname)) continue;
|
|
241
350
|
if (!pathname || pathname === "") {
|
|
@@ -243,160 +352,57 @@ function extractRoutes(pageKeys, localeSegment) {
|
|
|
243
352
|
} else if (!pathname.startsWith("/")) {
|
|
244
353
|
pathname = "/" + pathname;
|
|
245
354
|
}
|
|
246
|
-
const dynamicSegments = pathname.match(/\[([^\]]+)\]/g)?.map((s) => s.slice(1, -1))
|
|
247
|
-
routes.push({
|
|
248
|
-
pathname,
|
|
249
|
-
dynamicSegments,
|
|
250
|
-
key
|
|
251
|
-
});
|
|
355
|
+
const dynamicSegments = pathname.match(/\[([^\]]+)\]/g)?.map((s) => s.slice(1, -1)) ?? [];
|
|
356
|
+
routes.push({ pathname, dynamicSegments, fileKey: key });
|
|
252
357
|
}
|
|
253
358
|
return routes;
|
|
254
359
|
}
|
|
255
|
-
async function getAllPaths(routes, pagesDirectory, debug = false) {
|
|
256
|
-
const allPaths = ["/"];
|
|
257
|
-
const seenPaths = /* @__PURE__ */ new Set(["/"]);
|
|
258
|
-
for (const route of routes) {
|
|
259
|
-
if (route.dynamicSegments.length === 0) {
|
|
260
|
-
if (route.pathname !== "/" && !seenPaths.has(route.pathname)) {
|
|
261
|
-
allPaths.push(route.pathname);
|
|
262
|
-
seenPaths.add(route.pathname);
|
|
263
|
-
}
|
|
264
|
-
} else {
|
|
265
|
-
let getParams = null;
|
|
266
|
-
try {
|
|
267
|
-
if (debug) {
|
|
268
|
-
console.log(`[next-sitemap] ${route.pathname}: importing ${route.key}`);
|
|
269
|
-
}
|
|
270
|
-
const module = await importPage(pagesDirectory, route.key);
|
|
271
|
-
const getStaticPaths = module.getStaticPaths;
|
|
272
|
-
if (getStaticPaths) {
|
|
273
|
-
getParams = async () => {
|
|
274
|
-
const result = await getStaticPaths();
|
|
275
|
-
return result.paths.map((p) => p.params);
|
|
276
|
-
};
|
|
277
|
-
} else if (module.generateStaticParams) {
|
|
278
|
-
getParams = module.generateStaticParams;
|
|
279
|
-
}
|
|
280
|
-
} catch (error) {
|
|
281
|
-
if (debug) {
|
|
282
|
-
console.warn(`[next-sitemap] ${route.pathname}: import failed:`, error);
|
|
283
|
-
}
|
|
284
|
-
}
|
|
285
|
-
if (getParams) {
|
|
286
|
-
try {
|
|
287
|
-
const params = await getParams();
|
|
288
|
-
if (debug) {
|
|
289
|
-
console.log(`[next-sitemap] ${route.pathname}: getStaticPaths returned ${params.length} params`);
|
|
290
|
-
}
|
|
291
|
-
for (const param of params) {
|
|
292
|
-
let dynamicPath = route.pathname;
|
|
293
|
-
for (const segment of route.dynamicSegments) {
|
|
294
|
-
const value = param[segment];
|
|
295
|
-
if (value === void 0) {
|
|
296
|
-
if (debug) {
|
|
297
|
-
console.warn(`[next-sitemap] ${route.pathname}: missing param "${segment}" in`, param);
|
|
298
|
-
}
|
|
299
|
-
continue;
|
|
300
|
-
}
|
|
301
|
-
dynamicPath = dynamicPath.replace(`[${segment}]`, value);
|
|
302
|
-
}
|
|
303
|
-
if (!seenPaths.has(dynamicPath)) {
|
|
304
|
-
allPaths.push(dynamicPath);
|
|
305
|
-
seenPaths.add(dynamicPath);
|
|
306
|
-
}
|
|
307
|
-
}
|
|
308
|
-
} catch (error) {
|
|
309
|
-
console.error(`[next-sitemap] Error calling getStaticPaths for ${route.pathname}:`, error);
|
|
310
|
-
}
|
|
311
|
-
} else {
|
|
312
|
-
if (debug) {
|
|
313
|
-
console.warn(
|
|
314
|
-
`[next-sitemap] Skipping dynamic route ${route.pathname}: no getStaticPaths exported. Use additionalSitemaps for routes that fetch data at runtime.`
|
|
315
|
-
);
|
|
316
|
-
}
|
|
317
|
-
}
|
|
318
|
-
}
|
|
319
|
-
}
|
|
320
|
-
return allPaths;
|
|
321
|
-
}
|
|
322
|
-
function pathsToEntries(paths, config) {
|
|
323
|
-
const { baseUrl, locales = [], defaultLocale, exclude, priority, changeFreq } = config;
|
|
324
|
-
const filteredPaths = paths.filter((pathname) => !shouldExclude(pathname, exclude));
|
|
325
|
-
return filteredPaths.map((pathname) => {
|
|
326
|
-
const entry = {
|
|
327
|
-
url: buildUrl(baseUrl, pathname, defaultLocale, defaultLocale),
|
|
328
|
-
lastModified: /* @__PURE__ */ new Date(),
|
|
329
|
-
changeFrequency: getChangeFreq(pathname, changeFreq),
|
|
330
|
-
priority: getPriority(pathname, priority)
|
|
331
|
-
};
|
|
332
|
-
if (locales.length > 0) {
|
|
333
|
-
entry.alternates = {
|
|
334
|
-
languages: Object.fromEntries(
|
|
335
|
-
locales.map((locale) => [
|
|
336
|
-
locale,
|
|
337
|
-
buildUrl(baseUrl, pathname, locale, defaultLocale)
|
|
338
|
-
])
|
|
339
|
-
)
|
|
340
|
-
};
|
|
341
|
-
}
|
|
342
|
-
return entry;
|
|
343
|
-
});
|
|
344
|
-
}
|
|
345
360
|
function createSitemapIndexApiHandler(options) {
|
|
346
361
|
const { urlsPerSitemap = 5e3, additionalSitemaps, exclude, debug = false } = options;
|
|
347
362
|
const localeSegment = options.localeSegment ?? "";
|
|
348
|
-
const pagesDir = resolvePagesDirectory(options);
|
|
349
|
-
const pageKeys =
|
|
363
|
+
const pagesDir = resolvePagesDirectory(options.pagesDirectory);
|
|
364
|
+
const pageKeys = findPageFiles(pagesDir);
|
|
350
365
|
const routes = extractRoutes(pageKeys, localeSegment);
|
|
351
|
-
if (debug) {
|
|
352
|
-
console.log(`[next-sitemap] Found ${routes.length} routes:`);
|
|
353
|
-
routes.forEach((r) => {
|
|
354
|
-
const isDynamic = r.dynamicSegments.length > 0;
|
|
355
|
-
const segments = isDynamic ? ` [${r.dynamicSegments.join(", ")}]` : "";
|
|
356
|
-
console.log(` ${r.pathname}${segments}${isDynamic ? " (dynamic)" : ""}`);
|
|
357
|
-
});
|
|
358
|
-
}
|
|
359
366
|
return async function handler(_req, res) {
|
|
360
|
-
|
|
361
|
-
|
|
367
|
+
if (debug) {
|
|
368
|
+
console.log(`[next-sitemap] Found ${routes.length} routes`);
|
|
369
|
+
}
|
|
370
|
+
const allPaths = await generateAllPaths(routes, pagesDir, debug);
|
|
371
|
+
const filteredPaths = allPaths.filter((p) => !shouldExclude(p, exclude));
|
|
362
372
|
const sitemapCount = Math.max(1, Math.ceil(filteredPaths.length / urlsPerSitemap));
|
|
363
|
-
const xml = generateSitemapIndexXml(options.baseUrl, sitemapCount, {
|
|
364
|
-
additionalSitemaps
|
|
365
|
-
});
|
|
366
373
|
res.setHeader("Content-Type", "application/xml");
|
|
367
|
-
res.status(200).send(
|
|
374
|
+
res.status(200).send(
|
|
375
|
+
generateSitemapIndexXml(options.baseUrl, sitemapCount, { additionalSitemaps, poweredBy: options.poweredBy })
|
|
376
|
+
);
|
|
368
377
|
};
|
|
369
378
|
}
|
|
370
379
|
function createSitemapApiHandler(options) {
|
|
371
380
|
const { urlsPerSitemap = 5e3, exclude, debug = false } = options;
|
|
372
381
|
const localeSegment = options.localeSegment ?? "";
|
|
373
|
-
const pagesDir = resolvePagesDirectory(options);
|
|
374
|
-
const pageKeys =
|
|
382
|
+
const pagesDir = resolvePagesDirectory(options.pagesDirectory);
|
|
383
|
+
const pageKeys = findPageFiles(pagesDir);
|
|
375
384
|
const routes = extractRoutes(pageKeys, localeSegment);
|
|
376
385
|
const getFilteredPaths = async () => {
|
|
377
|
-
const allPaths = await
|
|
378
|
-
return allPaths.filter((
|
|
386
|
+
const allPaths = await generateAllPaths(routes, pagesDir, debug);
|
|
387
|
+
return allPaths.filter((p) => !shouldExclude(p, exclude));
|
|
379
388
|
};
|
|
380
389
|
return async function handler(req, res) {
|
|
381
390
|
const { id } = req.query;
|
|
382
391
|
const sitemapId = parseInt(Array.isArray(id) ? id[0] : id || "0", 10);
|
|
383
392
|
const filteredPaths = await getFilteredPaths();
|
|
384
|
-
const
|
|
385
|
-
const end = start + urlsPerSitemap;
|
|
386
|
-
const paths = filteredPaths.slice(start, end);
|
|
393
|
+
const paths = filteredPaths.slice(sitemapId * urlsPerSitemap, (sitemapId + 1) * urlsPerSitemap);
|
|
387
394
|
const entries = pathsToEntries(paths, { ...options, exclude: void 0 });
|
|
388
|
-
const xml = generateSitemapXml(entries);
|
|
389
395
|
res.setHeader("Content-Type", "application/xml");
|
|
390
|
-
res.status(200).send(
|
|
396
|
+
res.status(200).send(generateSitemapXml(entries, { poweredBy: options.poweredBy }));
|
|
391
397
|
};
|
|
392
398
|
}
|
|
393
399
|
async function getSitemapStaticPaths(options) {
|
|
394
400
|
const { urlsPerSitemap = 5e3, exclude, debug = false } = options;
|
|
395
401
|
const localeSegment = options.localeSegment ?? "";
|
|
396
|
-
const pagesDir = resolvePagesDirectory(options);
|
|
397
|
-
const pageKeys =
|
|
402
|
+
const pagesDir = resolvePagesDirectory(options.pagesDirectory);
|
|
403
|
+
const pageKeys = findPageFiles(pagesDir);
|
|
398
404
|
const routes = extractRoutes(pageKeys, localeSegment);
|
|
399
|
-
const allPaths = await
|
|
405
|
+
const allPaths = await generateAllPaths(routes, pagesDir, debug);
|
|
400
406
|
const filteredPaths = allPaths.filter((pathname) => !shouldExclude(pathname, exclude));
|
|
401
407
|
const sitemapCount = Math.max(1, Math.ceil(filteredPaths.length / urlsPerSitemap));
|
|
402
408
|
return {
|
|
@@ -408,9 +414,8 @@ async function getSitemapStaticPaths(options) {
|
|
|
408
414
|
}
|
|
409
415
|
function createRobotsApiHandler(config) {
|
|
410
416
|
return function handler(_req, res) {
|
|
411
|
-
const robotsTxt = generateRobotsTxt(config);
|
|
412
417
|
res.setHeader("Content-Type", "text/plain");
|
|
413
|
-
res.status(200).send(
|
|
418
|
+
res.status(200).send(generateRobotsTxt(config));
|
|
414
419
|
};
|
|
415
420
|
}
|
|
416
421
|
|