@onruntime/next-sitemap 0.2.0 → 0.3.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 +73 -0
- package/dist/app/index.cjs +55 -15
- package/dist/app/index.d.cts +6 -0
- package/dist/app/index.d.ts +6 -0
- package/dist/app/index.js +55 -15
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -118,6 +118,7 @@ export default nextConfig;
|
|
|
118
118
|
| `priority` | `number`, `"auto"`, or `function` | `"auto"` | Priority calculation (auto = depth-based) |
|
|
119
119
|
| `changeFreq` | `ChangeFrequency` or `function` | `"weekly"` | Change frequency for entries |
|
|
120
120
|
| `additionalSitemaps` | `string[]` | `[]` | Additional sitemaps to include in index |
|
|
121
|
+
| `debug` | `boolean` | `false` | Enable debug logging to diagnose route discovery issues |
|
|
121
122
|
|
|
122
123
|
#### Exclude Routes
|
|
123
124
|
|
|
@@ -266,6 +267,78 @@ export async function GET() {
|
|
|
266
267
|
</urlset>
|
|
267
268
|
```
|
|
268
269
|
|
|
270
|
+
## Troubleshooting
|
|
271
|
+
|
|
272
|
+
### Dynamic routes not included in sitemap
|
|
273
|
+
|
|
274
|
+
If your dynamic routes (e.g., `/articles/[slug]`) are not appearing in the sitemap, enable debug mode to diagnose:
|
|
275
|
+
|
|
276
|
+
```typescript
|
|
277
|
+
const { GET } = createSitemapIndexHandler({
|
|
278
|
+
baseUrl: "https://example.com",
|
|
279
|
+
pagesContext,
|
|
280
|
+
debug: true, // Enable debug logging
|
|
281
|
+
});
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
This will log:
|
|
285
|
+
- All discovered routes and whether `generateStaticParams` was found
|
|
286
|
+
- Number of params returned by each `generateStaticParams` call
|
|
287
|
+
- Any errors that occur during param generation
|
|
288
|
+
|
|
289
|
+
**Common issues:**
|
|
290
|
+
|
|
291
|
+
1. **`generateStaticParams` not detected**: Make sure it's exported as a named export at the top level of your page file:
|
|
292
|
+
```typescript
|
|
293
|
+
// ✅ Correct
|
|
294
|
+
export async function generateStaticParams() { ... }
|
|
295
|
+
|
|
296
|
+
// ❌ Wrong - not exported
|
|
297
|
+
async function generateStaticParams() { ... }
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
2. **Database/API errors**: If `generateStaticParams` fetches data from a database or API, errors are caught and logged. Check the console for error messages.
|
|
301
|
+
|
|
302
|
+
3. **Empty params returned**: If `generateStaticParams` returns an empty array, no dynamic paths will be generated.
|
|
303
|
+
|
|
304
|
+
### Recommended approach for API/Database routes
|
|
305
|
+
|
|
306
|
+
For routes that fetch data from external sources (APIs, databases like Payload CMS), we recommend using `additionalSitemaps` with a custom sitemap route:
|
|
307
|
+
|
|
308
|
+
```typescript
|
|
309
|
+
// app/sitemap.xml/route.ts
|
|
310
|
+
const { GET } = createSitemapIndexHandler({
|
|
311
|
+
baseUrl: "https://example.com",
|
|
312
|
+
pagesContext,
|
|
313
|
+
additionalSitemaps: ["/articles-sitemap.xml"],
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
// app/articles-sitemap.xml/route.ts
|
|
317
|
+
import { generateSitemapXml } from "@onruntime/next-sitemap";
|
|
318
|
+
import { getPayload } from "payload";
|
|
319
|
+
|
|
320
|
+
export async function GET() {
|
|
321
|
+
const payload = await getPayload({ config: configPromise });
|
|
322
|
+
const articles = await payload.find({
|
|
323
|
+
collection: "articles",
|
|
324
|
+
limit: 1000,
|
|
325
|
+
select: { slug: true, updatedAt: true },
|
|
326
|
+
});
|
|
327
|
+
|
|
328
|
+
const entries = articles.docs.map((article) => ({
|
|
329
|
+
url: `https://example.com/articles/${article.slug}`,
|
|
330
|
+
lastModified: article.updatedAt,
|
|
331
|
+
priority: 0.7,
|
|
332
|
+
}));
|
|
333
|
+
|
|
334
|
+
return new Response(generateSitemapXml(entries), {
|
|
335
|
+
headers: { "Content-Type": "application/xml" },
|
|
336
|
+
});
|
|
337
|
+
}
|
|
338
|
+
```
|
|
339
|
+
|
|
340
|
+
This approach gives you full control over data fetching and error handling.
|
|
341
|
+
|
|
269
342
|
## License
|
|
270
343
|
|
|
271
344
|
MIT
|
package/dist/app/index.cjs
CHANGED
|
@@ -100,8 +100,20 @@ function extractRoutes(pagesContext, localeSegment) {
|
|
|
100
100
|
for (const key of pagesContext.keys()) {
|
|
101
101
|
if (key.includes("[...")) continue;
|
|
102
102
|
const pageModule = pagesContext(key);
|
|
103
|
-
let pathname = key.replace("./", "/").replace("/page.tsx", "")
|
|
104
|
-
if (
|
|
103
|
+
let pathname = key.replace("./", "/").replace("/page.tsx", "");
|
|
104
|
+
if (localeSegment) {
|
|
105
|
+
pathname = pathname.replace(
|
|
106
|
+
new RegExp(`^/${localeSegment.replace(/[[\]]/g, "\\$&")}`),
|
|
107
|
+
""
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
pathname = pathname.replace(/\/\([^)]+\)/g, "");
|
|
111
|
+
if (/(?:^|\/)(src|app)(?:\/|$)/.test(pathname)) continue;
|
|
112
|
+
if (!pathname || pathname === "") {
|
|
113
|
+
pathname = "/";
|
|
114
|
+
} else if (!pathname.startsWith("/")) {
|
|
115
|
+
pathname = "/" + pathname;
|
|
116
|
+
}
|
|
105
117
|
const dynamicSegments = pathname.match(/\[([^\]]+)\]/g)?.map((s) => s.slice(1, -1)) || [];
|
|
106
118
|
routes.push({
|
|
107
119
|
pathname,
|
|
@@ -111,7 +123,7 @@ function extractRoutes(pagesContext, localeSegment) {
|
|
|
111
123
|
}
|
|
112
124
|
return routes;
|
|
113
125
|
}
|
|
114
|
-
async function getAllPaths(routes) {
|
|
126
|
+
async function getAllPaths(routes, debug = false) {
|
|
115
127
|
const allPaths = ["/"];
|
|
116
128
|
const seenPaths = /* @__PURE__ */ new Set(["/"]);
|
|
117
129
|
for (const route of routes) {
|
|
@@ -121,16 +133,36 @@ async function getAllPaths(routes) {
|
|
|
121
133
|
seenPaths.add(route.pathname);
|
|
122
134
|
}
|
|
123
135
|
} else if (route.getParams) {
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
dynamicPath = dynamicPath.replace(`[${segment}]`, param[segment]);
|
|
136
|
+
try {
|
|
137
|
+
const params = await route.getParams();
|
|
138
|
+
if (debug) {
|
|
139
|
+
console.log(`[next-sitemap] ${route.pathname}: generateStaticParams returned ${params.length} params`);
|
|
129
140
|
}
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
141
|
+
for (const param of params) {
|
|
142
|
+
let dynamicPath = route.pathname;
|
|
143
|
+
for (const segment of route.dynamicSegments) {
|
|
144
|
+
const value = param[segment];
|
|
145
|
+
if (value === void 0) {
|
|
146
|
+
if (debug) {
|
|
147
|
+
console.warn(`[next-sitemap] ${route.pathname}: missing param "${segment}" in`, param);
|
|
148
|
+
}
|
|
149
|
+
continue;
|
|
150
|
+
}
|
|
151
|
+
dynamicPath = dynamicPath.replace(`[${segment}]`, value);
|
|
152
|
+
}
|
|
153
|
+
if (!seenPaths.has(dynamicPath)) {
|
|
154
|
+
allPaths.push(dynamicPath);
|
|
155
|
+
seenPaths.add(dynamicPath);
|
|
156
|
+
}
|
|
133
157
|
}
|
|
158
|
+
} catch (error) {
|
|
159
|
+
console.error(`[next-sitemap] Error calling generateStaticParams for ${route.pathname}:`, error);
|
|
160
|
+
}
|
|
161
|
+
} else if (route.dynamicSegments.length > 0) {
|
|
162
|
+
if (debug) {
|
|
163
|
+
console.warn(
|
|
164
|
+
`[next-sitemap] Skipping dynamic route ${route.pathname}: no generateStaticParams exported. Use additionalSitemaps for routes that fetch data at runtime.`
|
|
165
|
+
);
|
|
134
166
|
}
|
|
135
167
|
}
|
|
136
168
|
}
|
|
@@ -160,12 +192,20 @@ function pathsToEntries(paths, config) {
|
|
|
160
192
|
});
|
|
161
193
|
}
|
|
162
194
|
function createSitemapIndexHandler(options) {
|
|
163
|
-
const { urlsPerSitemap = 5e3, locales = [], defaultLocale, additionalSitemaps, exclude } = options;
|
|
195
|
+
const { urlsPerSitemap = 5e3, locales = [], defaultLocale, additionalSitemaps, exclude, debug = false } = options;
|
|
164
196
|
const localeSegment = options.localeSegment ?? (locales.length > 0 || defaultLocale ? "[locale]" : "");
|
|
165
197
|
const routes = extractRoutes(options.pagesContext, localeSegment);
|
|
198
|
+
if (debug) {
|
|
199
|
+
console.log(`[next-sitemap] Found ${routes.length} routes:`);
|
|
200
|
+
routes.forEach((r) => {
|
|
201
|
+
const hasParams = r.getParams ? "\u2713 generateStaticParams" : "\u2717 no generateStaticParams";
|
|
202
|
+
const segments = r.dynamicSegments.length > 0 ? ` [${r.dynamicSegments.join(", ")}]` : "";
|
|
203
|
+
console.log(` ${r.pathname}${segments} - ${hasParams}`);
|
|
204
|
+
});
|
|
205
|
+
}
|
|
166
206
|
return {
|
|
167
207
|
GET: async () => {
|
|
168
|
-
const allPaths = await getAllPaths(routes);
|
|
208
|
+
const allPaths = await getAllPaths(routes, debug);
|
|
169
209
|
const filteredPaths = allPaths.filter((pathname) => !shouldExclude(pathname, exclude));
|
|
170
210
|
const sitemapCount = Math.max(1, Math.ceil(filteredPaths.length / urlsPerSitemap));
|
|
171
211
|
const xml = generateSitemapIndexXml(options.baseUrl, sitemapCount, {
|
|
@@ -178,11 +218,11 @@ function createSitemapIndexHandler(options) {
|
|
|
178
218
|
};
|
|
179
219
|
}
|
|
180
220
|
function createSitemapHandler(options) {
|
|
181
|
-
const { urlsPerSitemap = 5e3, locales = [], defaultLocale, exclude } = options;
|
|
221
|
+
const { urlsPerSitemap = 5e3, locales = [], defaultLocale, exclude, debug = false } = options;
|
|
182
222
|
const localeSegment = options.localeSegment ?? (locales.length > 0 || defaultLocale ? "[locale]" : "");
|
|
183
223
|
const routes = extractRoutes(options.pagesContext, localeSegment);
|
|
184
224
|
const getFilteredPaths = async () => {
|
|
185
|
-
const allPaths = await getAllPaths(routes);
|
|
225
|
+
const allPaths = await getAllPaths(routes, debug);
|
|
186
226
|
return allPaths.filter((pathname) => !shouldExclude(pathname, exclude));
|
|
187
227
|
};
|
|
188
228
|
return {
|
package/dist/app/index.d.cts
CHANGED
|
@@ -79,6 +79,12 @@ interface CreateSitemapHandlerOptions extends SitemapConfig {
|
|
|
79
79
|
keys: () => string[];
|
|
80
80
|
(key: string): PageModule;
|
|
81
81
|
};
|
|
82
|
+
/**
|
|
83
|
+
* Enable debug logging to diagnose issues with route discovery
|
|
84
|
+
* Logs info about generateStaticParams calls and skipped routes
|
|
85
|
+
* @default false
|
|
86
|
+
*/
|
|
87
|
+
debug?: boolean;
|
|
82
88
|
}
|
|
83
89
|
/**
|
|
84
90
|
* Create handlers for sitemap index route
|
package/dist/app/index.d.ts
CHANGED
|
@@ -79,6 +79,12 @@ interface CreateSitemapHandlerOptions extends SitemapConfig {
|
|
|
79
79
|
keys: () => string[];
|
|
80
80
|
(key: string): PageModule;
|
|
81
81
|
};
|
|
82
|
+
/**
|
|
83
|
+
* Enable debug logging to diagnose issues with route discovery
|
|
84
|
+
* Logs info about generateStaticParams calls and skipped routes
|
|
85
|
+
* @default false
|
|
86
|
+
*/
|
|
87
|
+
debug?: boolean;
|
|
82
88
|
}
|
|
83
89
|
/**
|
|
84
90
|
* Create handlers for sitemap index route
|
package/dist/app/index.js
CHANGED
|
@@ -98,8 +98,20 @@ function extractRoutes(pagesContext, localeSegment) {
|
|
|
98
98
|
for (const key of pagesContext.keys()) {
|
|
99
99
|
if (key.includes("[...")) continue;
|
|
100
100
|
const pageModule = pagesContext(key);
|
|
101
|
-
let pathname = key.replace("./", "/").replace("/page.tsx", "")
|
|
102
|
-
if (
|
|
101
|
+
let pathname = key.replace("./", "/").replace("/page.tsx", "");
|
|
102
|
+
if (localeSegment) {
|
|
103
|
+
pathname = pathname.replace(
|
|
104
|
+
new RegExp(`^/${localeSegment.replace(/[[\]]/g, "\\$&")}`),
|
|
105
|
+
""
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
pathname = pathname.replace(/\/\([^)]+\)/g, "");
|
|
109
|
+
if (/(?:^|\/)(src|app)(?:\/|$)/.test(pathname)) continue;
|
|
110
|
+
if (!pathname || pathname === "") {
|
|
111
|
+
pathname = "/";
|
|
112
|
+
} else if (!pathname.startsWith("/")) {
|
|
113
|
+
pathname = "/" + pathname;
|
|
114
|
+
}
|
|
103
115
|
const dynamicSegments = pathname.match(/\[([^\]]+)\]/g)?.map((s) => s.slice(1, -1)) || [];
|
|
104
116
|
routes.push({
|
|
105
117
|
pathname,
|
|
@@ -109,7 +121,7 @@ function extractRoutes(pagesContext, localeSegment) {
|
|
|
109
121
|
}
|
|
110
122
|
return routes;
|
|
111
123
|
}
|
|
112
|
-
async function getAllPaths(routes) {
|
|
124
|
+
async function getAllPaths(routes, debug = false) {
|
|
113
125
|
const allPaths = ["/"];
|
|
114
126
|
const seenPaths = /* @__PURE__ */ new Set(["/"]);
|
|
115
127
|
for (const route of routes) {
|
|
@@ -119,16 +131,36 @@ async function getAllPaths(routes) {
|
|
|
119
131
|
seenPaths.add(route.pathname);
|
|
120
132
|
}
|
|
121
133
|
} else if (route.getParams) {
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
dynamicPath = dynamicPath.replace(`[${segment}]`, param[segment]);
|
|
134
|
+
try {
|
|
135
|
+
const params = await route.getParams();
|
|
136
|
+
if (debug) {
|
|
137
|
+
console.log(`[next-sitemap] ${route.pathname}: generateStaticParams returned ${params.length} params`);
|
|
127
138
|
}
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
139
|
+
for (const param of params) {
|
|
140
|
+
let dynamicPath = route.pathname;
|
|
141
|
+
for (const segment of route.dynamicSegments) {
|
|
142
|
+
const value = param[segment];
|
|
143
|
+
if (value === void 0) {
|
|
144
|
+
if (debug) {
|
|
145
|
+
console.warn(`[next-sitemap] ${route.pathname}: missing param "${segment}" in`, param);
|
|
146
|
+
}
|
|
147
|
+
continue;
|
|
148
|
+
}
|
|
149
|
+
dynamicPath = dynamicPath.replace(`[${segment}]`, value);
|
|
150
|
+
}
|
|
151
|
+
if (!seenPaths.has(dynamicPath)) {
|
|
152
|
+
allPaths.push(dynamicPath);
|
|
153
|
+
seenPaths.add(dynamicPath);
|
|
154
|
+
}
|
|
131
155
|
}
|
|
156
|
+
} catch (error) {
|
|
157
|
+
console.error(`[next-sitemap] Error calling generateStaticParams for ${route.pathname}:`, error);
|
|
158
|
+
}
|
|
159
|
+
} else if (route.dynamicSegments.length > 0) {
|
|
160
|
+
if (debug) {
|
|
161
|
+
console.warn(
|
|
162
|
+
`[next-sitemap] Skipping dynamic route ${route.pathname}: no generateStaticParams exported. Use additionalSitemaps for routes that fetch data at runtime.`
|
|
163
|
+
);
|
|
132
164
|
}
|
|
133
165
|
}
|
|
134
166
|
}
|
|
@@ -158,12 +190,20 @@ function pathsToEntries(paths, config) {
|
|
|
158
190
|
});
|
|
159
191
|
}
|
|
160
192
|
function createSitemapIndexHandler(options) {
|
|
161
|
-
const { urlsPerSitemap = 5e3, locales = [], defaultLocale, additionalSitemaps, exclude } = options;
|
|
193
|
+
const { urlsPerSitemap = 5e3, locales = [], defaultLocale, additionalSitemaps, exclude, debug = false } = options;
|
|
162
194
|
const localeSegment = options.localeSegment ?? (locales.length > 0 || defaultLocale ? "[locale]" : "");
|
|
163
195
|
const routes = extractRoutes(options.pagesContext, localeSegment);
|
|
196
|
+
if (debug) {
|
|
197
|
+
console.log(`[next-sitemap] Found ${routes.length} routes:`);
|
|
198
|
+
routes.forEach((r) => {
|
|
199
|
+
const hasParams = r.getParams ? "\u2713 generateStaticParams" : "\u2717 no generateStaticParams";
|
|
200
|
+
const segments = r.dynamicSegments.length > 0 ? ` [${r.dynamicSegments.join(", ")}]` : "";
|
|
201
|
+
console.log(` ${r.pathname}${segments} - ${hasParams}`);
|
|
202
|
+
});
|
|
203
|
+
}
|
|
164
204
|
return {
|
|
165
205
|
GET: async () => {
|
|
166
|
-
const allPaths = await getAllPaths(routes);
|
|
206
|
+
const allPaths = await getAllPaths(routes, debug);
|
|
167
207
|
const filteredPaths = allPaths.filter((pathname) => !shouldExclude(pathname, exclude));
|
|
168
208
|
const sitemapCount = Math.max(1, Math.ceil(filteredPaths.length / urlsPerSitemap));
|
|
169
209
|
const xml = generateSitemapIndexXml(options.baseUrl, sitemapCount, {
|
|
@@ -176,11 +216,11 @@ function createSitemapIndexHandler(options) {
|
|
|
176
216
|
};
|
|
177
217
|
}
|
|
178
218
|
function createSitemapHandler(options) {
|
|
179
|
-
const { urlsPerSitemap = 5e3, locales = [], defaultLocale, exclude } = options;
|
|
219
|
+
const { urlsPerSitemap = 5e3, locales = [], defaultLocale, exclude, debug = false } = options;
|
|
180
220
|
const localeSegment = options.localeSegment ?? (locales.length > 0 || defaultLocale ? "[locale]" : "");
|
|
181
221
|
const routes = extractRoutes(options.pagesContext, localeSegment);
|
|
182
222
|
const getFilteredPaths = async () => {
|
|
183
|
-
const allPaths = await getAllPaths(routes);
|
|
223
|
+
const allPaths = await getAllPaths(routes, debug);
|
|
184
224
|
return allPaths.filter((pathname) => !shouldExclude(pathname, exclude));
|
|
185
225
|
};
|
|
186
226
|
return {
|
package/package.json
CHANGED