@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 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
@@ -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", "").replace(new RegExp(`^/${localeSegment.replace(/[[\]]/g, "\\$&")}`), "").replace(/\/\([^)]+\)/g, "") || "/";
104
- if (pathname === "") pathname = "/";
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
- const params = await route.getParams();
125
- for (const param of params) {
126
- let dynamicPath = route.pathname;
127
- for (const segment of route.dynamicSegments) {
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
- if (!seenPaths.has(dynamicPath)) {
131
- allPaths.push(dynamicPath);
132
- seenPaths.add(dynamicPath);
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 {
@@ -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
@@ -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", "").replace(new RegExp(`^/${localeSegment.replace(/[[\]]/g, "\\$&")}`), "").replace(/\/\([^)]+\)/g, "") || "/";
102
- if (pathname === "") pathname = "/";
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
- const params = await route.getParams();
123
- for (const param of params) {
124
- let dynamicPath = route.pathname;
125
- for (const segment of route.dynamicSegments) {
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
- if (!seenPaths.has(dynamicPath)) {
129
- allPaths.push(dynamicPath);
130
- seenPaths.add(dynamicPath);
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@onruntime/next-sitemap",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "Dynamic sitemap generation for Next.js with automatic route discovery",
5
5
  "author": "onRuntime Studio <contact@onruntime.com>",
6
6
  "repository": {