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