@onruntime/next-sitemap 0.5.0 → 0.6.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
@@ -12,7 +12,7 @@ Dynamic sitemap generation for Next.js with automatic route discovery.
12
12
  ## Features
13
13
 
14
14
  - **App Router** and **Pages Router** support
15
- - Automatic route discovery using `require.context`
15
+ - Automatic route discovery using filesystem scan
16
16
  - Calls `generateStaticParams` (App Router) or `getStaticPaths` (Pages Router) for dynamic routes
17
17
  - Multi-sitemap support with sitemap index (for sites with >50,000 URLs)
18
18
  - hreflang alternates for i18n
@@ -37,12 +37,8 @@ import { createSitemapIndexHandler } from "@onruntime/next-sitemap/app";
37
37
 
38
38
  export const dynamic = "force-static";
39
39
 
40
- // @ts-expect-error - require.context is a webpack/turbopack feature
41
- const pagesContext = require.context("../", true, /\/page\.tsx$/);
42
-
43
40
  const { GET } = createSitemapIndexHandler({
44
41
  baseUrl: "https://example.com",
45
- pagesContext,
46
42
  });
47
43
 
48
44
  export { GET };
@@ -56,12 +52,8 @@ import { createSitemapHandler } from "@onruntime/next-sitemap/app";
56
52
 
57
53
  export const dynamic = "force-static";
58
54
 
59
- // @ts-expect-error - require.context is a webpack/turbopack feature
60
- const pagesContext = require.context("../../", true, /\/page\.tsx$/);
61
-
62
55
  const { generateStaticParams, GET } = createSitemapHandler({
63
56
  baseUrl: "https://example.com",
64
- pagesContext,
65
57
  });
66
58
 
67
59
  export { generateStaticParams, GET };
@@ -73,13 +65,10 @@ If your app uses a `[locale]` segment for internationalization, just add `locale
73
65
 
74
66
  ```typescript
75
67
  // app/sitemap.xml/route.ts
76
- const pagesContext = require.context("../[locale]", true, /\/page\.tsx$/);
77
-
78
68
  const { GET } = createSitemapIndexHandler({
79
69
  baseUrl: "https://example.com",
80
70
  locales: ["en", "fr"],
81
71
  defaultLocale: "en",
82
- pagesContext,
83
72
  });
84
73
  ```
85
74
 
@@ -117,12 +106,8 @@ export default nextConfig;
117
106
  // pages/api/sitemap.xml.ts
118
107
  import { createSitemapIndexApiHandler } from "@onruntime/next-sitemap/pages";
119
108
 
120
- // @ts-expect-error - require.context is a webpack/turbopack feature
121
- const pagesContext = require.context("../", true, /^\.\/(?!\[|_|api\/).*\.tsx$/);
122
-
123
109
  export default createSitemapIndexApiHandler({
124
110
  baseUrl: "https://example.com",
125
- pagesContext,
126
111
  });
127
112
  ```
128
113
 
@@ -132,12 +117,8 @@ export default createSitemapIndexApiHandler({
132
117
  // pages/api/sitemap/[id].ts
133
118
  import { createSitemapApiHandler } from "@onruntime/next-sitemap/pages";
134
119
 
135
- // @ts-expect-error - require.context is a webpack/turbopack feature
136
- const pagesContext = require.context("../../", true, /^\.\/(?!\[|_|api\/).*\.tsx$/);
137
-
138
120
  export default createSitemapApiHandler({
139
121
  baseUrl: "https://example.com",
140
- pagesContext,
141
122
  });
142
123
  ```
143
124
 
@@ -181,14 +162,10 @@ module.exports = {
181
162
  // pages/api/sitemap.xml.ts
182
163
  import { createSitemapIndexApiHandler } from "@onruntime/next-sitemap/pages";
183
164
 
184
- // @ts-expect-error - require.context is a webpack/turbopack feature
185
- const pagesContext = require.context("../", true, /^\.\/(?!\[|_|api\/).*\.tsx$/);
186
-
187
165
  export default createSitemapIndexApiHandler({
188
166
  baseUrl: "https://example.com",
189
167
  locales: ["en", "fr"],
190
168
  defaultLocale: "en",
191
- pagesContext,
192
169
  });
193
170
  ```
194
171
 
@@ -197,11 +174,12 @@ export default createSitemapIndexApiHandler({
197
174
  | Option | Type | Default | Description |
198
175
  |--------|------|---------|-------------|
199
176
  | `baseUrl` | `string` | required | Base URL of the site |
177
+ | `appDirectory` | `string` | auto | (App Router) Path to scan for pages. Auto-detects `src/app` or `app` |
178
+ | `pagesDirectory` | `string` | auto | (Pages Router) Path to scan for pages. Auto-detects `src/pages` or `pages` |
200
179
  | `locales` | `string[]` | `[]` | List of supported locales |
201
180
  | `defaultLocale` | `string` | `undefined` | Default locale (URLs without prefix) |
202
181
  | `urlsPerSitemap` | `number` | `5000` | Max URLs per sitemap file |
203
182
  | `localeSegment` | `string` | auto | Auto-detected as `"[locale]"` when i18n is configured. Override for custom names like `"[lang]"`. |
204
- | `pagesContext` | `object` | required | Result of `require.context()` |
205
183
  | `exclude` | `string[]` or `function` | `undefined` | Patterns or function to exclude routes |
206
184
  | `priority` | `number`, `"auto"`, or `function` | `"auto"` | Priority calculation (auto = depth-based) |
207
185
  | `changeFreq` | `ChangeFrequency` or `function` | `"weekly"` | Change frequency for entries |
@@ -216,14 +194,12 @@ Exclude specific routes from the sitemap using glob patterns or a function:
216
194
  // Using glob patterns
217
195
  const { GET } = createSitemapIndexHandler({
218
196
  baseUrl: "https://example.com",
219
- pagesContext,
220
197
  exclude: ["/admin/*", "/api/*", "/private/**"],
221
198
  });
222
199
 
223
200
  // Using a function
224
201
  const { GET } = createSitemapIndexHandler({
225
202
  baseUrl: "https://example.com",
226
- pagesContext,
227
203
  exclude: (path) => path.startsWith("/internal"),
228
204
  });
229
205
  ```
@@ -273,14 +249,13 @@ Include custom sitemaps in the sitemap index (e.g., for API-fetched data):
273
249
  ```typescript
274
250
  const { GET } = createSitemapIndexHandler({
275
251
  baseUrl: "https://example.com",
276
- pagesContext,
277
252
  additionalSitemaps: ["/products-sitemap.xml", "/blog-sitemap.xml"],
278
253
  });
279
254
  ```
280
255
 
281
256
  ### How It Works
282
257
 
283
- 1. `require.context` scans your app/pages directory at build time
258
+ 1. The filesystem is scanned to discover all pages in your app/pages directory
284
259
  2. For each page found, it extracts the route path
285
260
  3. For dynamic routes (e.g., `/projects/[id]`), it calls `generateStaticParams` (App Router) or `getStaticPaths` (Pages Router)
286
261
  4. URLs are paginated into multiple sitemaps (default: 5000 URLs each)
@@ -405,7 +380,6 @@ If your dynamic routes (e.g., `/articles/[slug]`) are not appearing in the sitem
405
380
  ```typescript
406
381
  const { GET } = createSitemapIndexHandler({
407
382
  baseUrl: "https://example.com",
408
- pagesContext,
409
383
  debug: true, // Enable debug logging
410
384
  });
411
385
  ```
@@ -441,7 +415,6 @@ For routes that fetch data from external sources (APIs, databases like Payload C
441
415
  // app/sitemap.xml/route.ts
442
416
  const { GET } = createSitemapIndexHandler({
443
417
  baseUrl: "https://example.com",
444
- pagesContext,
445
418
  additionalSitemaps: ["/articles-sitemap.xml"],
446
419
  });
447
420
 
@@ -1,5 +1,33 @@
1
1
  'use strict';
2
2
 
3
+ var fs = require('fs');
4
+ var path = require('path');
5
+ var jiti$1 = require('jiti');
6
+
7
+ var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
8
+ function _interopNamespace(e) {
9
+ if (e && e.__esModule) return e;
10
+ var n = Object.create(null);
11
+ if (e) {
12
+ Object.keys(e).forEach(function (k) {
13
+ if (k !== 'default') {
14
+ var d = Object.getOwnPropertyDescriptor(e, k);
15
+ Object.defineProperty(n, k, d.get ? d : {
16
+ enumerable: true,
17
+ get: function () { return e[k]; }
18
+ });
19
+ }
20
+ });
21
+ }
22
+ n.default = e;
23
+ return Object.freeze(n);
24
+ }
25
+
26
+ var fs__namespace = /*#__PURE__*/_interopNamespace(fs);
27
+ var path__namespace = /*#__PURE__*/_interopNamespace(path);
28
+
29
+ // src/app/index.ts
30
+
3
31
  // src/index.ts
4
32
  function calculateDepthPriority(pathname) {
5
33
  if (pathname === "/") return 1;
@@ -101,11 +129,49 @@ ${allEntries}
101
129
  }
102
130
 
103
131
  // src/app/index.ts
104
- function extractRoutes(pagesContext, localeSegment) {
132
+ function findPageFiles(dir, baseDir = dir) {
133
+ const files = [];
134
+ try {
135
+ const entries = fs__namespace.readdirSync(dir, { withFileTypes: true });
136
+ for (const entry of entries) {
137
+ const fullPath = path__namespace.join(dir, entry.name);
138
+ if (entry.isDirectory()) {
139
+ files.push(...findPageFiles(fullPath, baseDir));
140
+ } else if (entry.name === "page.tsx" || entry.name === "page.ts") {
141
+ const relativePath = "./" + path__namespace.relative(baseDir, fullPath).replace(/\\/g, "/");
142
+ files.push(relativePath);
143
+ }
144
+ }
145
+ } catch {
146
+ }
147
+ return files;
148
+ }
149
+ function detectAppDirectory() {
150
+ const srcApp = path__namespace.join(process.cwd(), "src/app");
151
+ if (fs__namespace.existsSync(srcApp)) {
152
+ return srcApp;
153
+ }
154
+ return path__namespace.join(process.cwd(), "app");
155
+ }
156
+ function resolveAppDirectory(options) {
157
+ if (options.appDirectory) {
158
+ return path__namespace.isAbsolute(options.appDirectory) ? options.appDirectory : path__namespace.join(process.cwd(), options.appDirectory);
159
+ }
160
+ return detectAppDirectory();
161
+ }
162
+ function getPageKeys(options) {
163
+ return findPageFiles(resolveAppDirectory(options));
164
+ }
165
+ var jiti = jiti$1.createJiti((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href)), { jsx: true });
166
+ async function importPage(appDirectory, key) {
167
+ const relativePath = key.replace("./", "");
168
+ const absolutePath = path__namespace.join(appDirectory, relativePath);
169
+ return jiti.import(absolutePath);
170
+ }
171
+ function extractRoutes(pageKeys, localeSegment) {
105
172
  const routes = [];
106
- for (const key of pagesContext.keys()) {
173
+ for (const key of pageKeys) {
107
174
  if (key.includes("[...")) continue;
108
- const pageModule = pagesContext(key);
109
175
  let pathname = key.replace("./", "/").replace("/page.tsx", "");
110
176
  if (localeSegment) {
111
177
  pathname = pathname.replace(
@@ -124,12 +190,12 @@ function extractRoutes(pagesContext, localeSegment) {
124
190
  routes.push({
125
191
  pathname,
126
192
  dynamicSegments,
127
- getParams: pageModule.generateStaticParams || null
193
+ key
128
194
  });
129
195
  }
130
196
  return routes;
131
197
  }
132
- async function getAllPaths(routes, debug = false) {
198
+ async function getAllPaths(routes, appDirectory, debug = false) {
133
199
  const allPaths = ["/"];
134
200
  const seenPaths = /* @__PURE__ */ new Set(["/"]);
135
201
  for (const route of routes) {
@@ -138,37 +204,51 @@ async function getAllPaths(routes, debug = false) {
138
204
  allPaths.push(route.pathname);
139
205
  seenPaths.add(route.pathname);
140
206
  }
141
- } else if (route.getParams) {
207
+ } else {
208
+ let getParams = null;
142
209
  try {
143
- const params = await route.getParams();
144
210
  if (debug) {
145
- console.log(`[next-sitemap] ${route.pathname}: generateStaticParams returned ${params.length} params`);
211
+ console.log(`[next-sitemap] ${route.pathname}: importing ${route.key}`);
146
212
  }
147
- for (const param of params) {
148
- let dynamicPath = route.pathname;
149
- for (const segment of route.dynamicSegments) {
150
- const value = param[segment];
151
- if (value === void 0) {
152
- if (debug) {
153
- console.warn(`[next-sitemap] ${route.pathname}: missing param "${segment}" in`, param);
213
+ const module = await importPage(appDirectory, route.key);
214
+ getParams = module.generateStaticParams || null;
215
+ } catch (error) {
216
+ if (debug) {
217
+ console.warn(`[next-sitemap] ${route.pathname}: import failed:`, error);
218
+ }
219
+ }
220
+ if (getParams) {
221
+ try {
222
+ const params = await getParams();
223
+ if (debug) {
224
+ console.log(`[next-sitemap] ${route.pathname}: generateStaticParams returned ${params.length} params`);
225
+ }
226
+ for (const param of params) {
227
+ let dynamicPath = route.pathname;
228
+ for (const segment of route.dynamicSegments) {
229
+ const value = param[segment];
230
+ if (value === void 0) {
231
+ if (debug) {
232
+ console.warn(`[next-sitemap] ${route.pathname}: missing param "${segment}" in`, param);
233
+ }
234
+ continue;
154
235
  }
155
- continue;
236
+ dynamicPath = dynamicPath.replace(`[${segment}]`, value);
237
+ }
238
+ if (!seenPaths.has(dynamicPath)) {
239
+ allPaths.push(dynamicPath);
240
+ seenPaths.add(dynamicPath);
156
241
  }
157
- dynamicPath = dynamicPath.replace(`[${segment}]`, value);
158
- }
159
- if (!seenPaths.has(dynamicPath)) {
160
- allPaths.push(dynamicPath);
161
- seenPaths.add(dynamicPath);
162
242
  }
243
+ } catch (error) {
244
+ console.error(`[next-sitemap] Error calling generateStaticParams for ${route.pathname}:`, error);
245
+ }
246
+ } else {
247
+ if (debug) {
248
+ console.warn(
249
+ `[next-sitemap] Skipping dynamic route ${route.pathname}: no generateStaticParams exported. Use additionalSitemaps for routes that fetch data at runtime.`
250
+ );
163
251
  }
164
- } catch (error) {
165
- console.error(`[next-sitemap] Error calling generateStaticParams for ${route.pathname}:`, error);
166
- }
167
- } else if (route.dynamicSegments.length > 0) {
168
- if (debug) {
169
- console.warn(
170
- `[next-sitemap] Skipping dynamic route ${route.pathname}: no generateStaticParams exported. Use additionalSitemaps for routes that fetch data at runtime.`
171
- );
172
252
  }
173
253
  }
174
254
  }
@@ -200,18 +280,20 @@ function pathsToEntries(paths, config) {
200
280
  function createSitemapIndexHandler(options) {
201
281
  const { urlsPerSitemap = 5e3, locales = [], defaultLocale, additionalSitemaps, exclude, debug = false } = options;
202
282
  const localeSegment = options.localeSegment ?? (locales.length > 0 || defaultLocale ? "[locale]" : "");
203
- const routes = extractRoutes(options.pagesContext, localeSegment);
204
- if (debug) {
205
- console.log(`[next-sitemap] Found ${routes.length} routes:`);
206
- routes.forEach((r) => {
207
- const hasParams = r.getParams ? "\u2713 generateStaticParams" : "\u2717 no generateStaticParams";
208
- const segments = r.dynamicSegments.length > 0 ? ` [${r.dynamicSegments.join(", ")}]` : "";
209
- console.log(` ${r.pathname}${segments} - ${hasParams}`);
210
- });
211
- }
283
+ const appDir = resolveAppDirectory(options);
284
+ const pageKeys = getPageKeys(options);
285
+ const routes = extractRoutes(pageKeys, localeSegment);
212
286
  return {
213
287
  GET: async () => {
214
- const allPaths = await getAllPaths(routes, debug);
288
+ if (debug) {
289
+ console.log(`[next-sitemap] Found ${routes.length} routes:`);
290
+ routes.forEach((r) => {
291
+ const isDynamic = r.dynamicSegments.length > 0;
292
+ const segments = isDynamic ? ` [${r.dynamicSegments.join(", ")}]` : "";
293
+ console.log(` ${r.pathname}${segments}${isDynamic ? " (dynamic)" : ""}`);
294
+ });
295
+ }
296
+ const allPaths = await getAllPaths(routes, appDir, debug);
215
297
  const filteredPaths = allPaths.filter((pathname) => !shouldExclude(pathname, exclude));
216
298
  const sitemapCount = Math.max(1, Math.ceil(filteredPaths.length / urlsPerSitemap));
217
299
  const xml = generateSitemapIndexXml(options.baseUrl, sitemapCount, {
@@ -226,9 +308,11 @@ function createSitemapIndexHandler(options) {
226
308
  function createSitemapHandler(options) {
227
309
  const { urlsPerSitemap = 5e3, locales = [], defaultLocale, exclude, debug = false } = options;
228
310
  const localeSegment = options.localeSegment ?? (locales.length > 0 || defaultLocale ? "[locale]" : "");
229
- const routes = extractRoutes(options.pagesContext, localeSegment);
311
+ const appDir = resolveAppDirectory(options);
312
+ const pageKeys = getPageKeys(options);
313
+ const routes = extractRoutes(pageKeys, localeSegment);
230
314
  const getFilteredPaths = async () => {
231
- const allPaths = await getAllPaths(routes, debug);
315
+ const allPaths = await getAllPaths(routes, appDir, debug);
232
316
  return allPaths.filter((pathname) => !shouldExclude(pathname, exclude));
233
317
  };
234
318
  return {
@@ -16,11 +16,6 @@ interface SitemapConfig {
16
16
  * Number of URLs per sitemap file (default: 5000)
17
17
  */
18
18
  urlsPerSitemap?: number;
19
- /**
20
- * Path to the app directory relative to the project root
21
- * Default: "./src/app" or "./app"
22
- */
23
- appDir?: string;
24
19
  /**
25
20
  * Locale segment in the URL path (e.g., "[locale]")
26
21
  * Auto-detected as "[locale]" when locales or defaultLocale is set
@@ -66,19 +61,19 @@ interface SitemapEntry {
66
61
  languages?: Record<string, string>;
67
62
  };
68
63
  }
69
- type PageModule = {
70
- generateStaticParams?: () => Promise<Record<string, string>[]>;
71
- };
72
64
 
73
65
  interface CreateSitemapHandlerOptions extends SitemapConfig {
74
66
  /**
75
- * The require.context result for page discovery
76
- * Example: require.context('./[locale]', true, /\/page\.tsx$/)
67
+ * Path to the app directory to scan for page.tsx files.
68
+ * Can be absolute or relative to process.cwd().
69
+ * If not provided, auto-detects src/app or app.
70
+ *
71
+ * Example:
72
+ * ```ts
73
+ * appDirectory: 'src/app/(frontend)'
74
+ * ```
77
75
  */
78
- pagesContext: {
79
- keys: () => string[];
80
- (key: string): PageModule;
81
- };
76
+ appDirectory?: string;
82
77
  /**
83
78
  * Enable debug logging to diagnose issues with route discovery
84
79
  * Logs info about generateStaticParams calls and skipped routes
@@ -16,11 +16,6 @@ interface SitemapConfig {
16
16
  * Number of URLs per sitemap file (default: 5000)
17
17
  */
18
18
  urlsPerSitemap?: number;
19
- /**
20
- * Path to the app directory relative to the project root
21
- * Default: "./src/app" or "./app"
22
- */
23
- appDir?: string;
24
19
  /**
25
20
  * Locale segment in the URL path (e.g., "[locale]")
26
21
  * Auto-detected as "[locale]" when locales or defaultLocale is set
@@ -66,19 +61,19 @@ interface SitemapEntry {
66
61
  languages?: Record<string, string>;
67
62
  };
68
63
  }
69
- type PageModule = {
70
- generateStaticParams?: () => Promise<Record<string, string>[]>;
71
- };
72
64
 
73
65
  interface CreateSitemapHandlerOptions extends SitemapConfig {
74
66
  /**
75
- * The require.context result for page discovery
76
- * Example: require.context('./[locale]', true, /\/page\.tsx$/)
67
+ * Path to the app directory to scan for page.tsx files.
68
+ * Can be absolute or relative to process.cwd().
69
+ * If not provided, auto-detects src/app or app.
70
+ *
71
+ * Example:
72
+ * ```ts
73
+ * appDirectory: 'src/app/(frontend)'
74
+ * ```
77
75
  */
78
- pagesContext: {
79
- keys: () => string[];
80
- (key: string): PageModule;
81
- };
76
+ appDirectory?: string;
82
77
  /**
83
78
  * Enable debug logging to diagnose issues with route discovery
84
79
  * Logs info about generateStaticParams calls and skipped routes
package/dist/app/index.js CHANGED
@@ -1,3 +1,9 @@
1
+ import * as fs from 'fs';
2
+ import * as path from 'path';
3
+ import { createJiti } from 'jiti';
4
+
5
+ // src/app/index.ts
6
+
1
7
  // src/index.ts
2
8
  function calculateDepthPriority(pathname) {
3
9
  if (pathname === "/") return 1;
@@ -99,11 +105,49 @@ ${allEntries}
99
105
  }
100
106
 
101
107
  // src/app/index.ts
102
- function extractRoutes(pagesContext, localeSegment) {
108
+ function findPageFiles(dir, baseDir = dir) {
109
+ const files = [];
110
+ try {
111
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
112
+ for (const entry of entries) {
113
+ const fullPath = path.join(dir, entry.name);
114
+ if (entry.isDirectory()) {
115
+ files.push(...findPageFiles(fullPath, baseDir));
116
+ } else if (entry.name === "page.tsx" || entry.name === "page.ts") {
117
+ const relativePath = "./" + path.relative(baseDir, fullPath).replace(/\\/g, "/");
118
+ files.push(relativePath);
119
+ }
120
+ }
121
+ } catch {
122
+ }
123
+ return files;
124
+ }
125
+ function detectAppDirectory() {
126
+ const srcApp = path.join(process.cwd(), "src/app");
127
+ if (fs.existsSync(srcApp)) {
128
+ return srcApp;
129
+ }
130
+ return path.join(process.cwd(), "app");
131
+ }
132
+ function resolveAppDirectory(options) {
133
+ if (options.appDirectory) {
134
+ return path.isAbsolute(options.appDirectory) ? options.appDirectory : path.join(process.cwd(), options.appDirectory);
135
+ }
136
+ return detectAppDirectory();
137
+ }
138
+ function getPageKeys(options) {
139
+ return findPageFiles(resolveAppDirectory(options));
140
+ }
141
+ var jiti = createJiti(import.meta.url, { jsx: true });
142
+ async function importPage(appDirectory, key) {
143
+ const relativePath = key.replace("./", "");
144
+ const absolutePath = path.join(appDirectory, relativePath);
145
+ return jiti.import(absolutePath);
146
+ }
147
+ function extractRoutes(pageKeys, localeSegment) {
103
148
  const routes = [];
104
- for (const key of pagesContext.keys()) {
149
+ for (const key of pageKeys) {
105
150
  if (key.includes("[...")) continue;
106
- const pageModule = pagesContext(key);
107
151
  let pathname = key.replace("./", "/").replace("/page.tsx", "");
108
152
  if (localeSegment) {
109
153
  pathname = pathname.replace(
@@ -122,12 +166,12 @@ function extractRoutes(pagesContext, localeSegment) {
122
166
  routes.push({
123
167
  pathname,
124
168
  dynamicSegments,
125
- getParams: pageModule.generateStaticParams || null
169
+ key
126
170
  });
127
171
  }
128
172
  return routes;
129
173
  }
130
- async function getAllPaths(routes, debug = false) {
174
+ async function getAllPaths(routes, appDirectory, debug = false) {
131
175
  const allPaths = ["/"];
132
176
  const seenPaths = /* @__PURE__ */ new Set(["/"]);
133
177
  for (const route of routes) {
@@ -136,37 +180,51 @@ async function getAllPaths(routes, debug = false) {
136
180
  allPaths.push(route.pathname);
137
181
  seenPaths.add(route.pathname);
138
182
  }
139
- } else if (route.getParams) {
183
+ } else {
184
+ let getParams = null;
140
185
  try {
141
- const params = await route.getParams();
142
186
  if (debug) {
143
- console.log(`[next-sitemap] ${route.pathname}: generateStaticParams returned ${params.length} params`);
187
+ console.log(`[next-sitemap] ${route.pathname}: importing ${route.key}`);
144
188
  }
145
- for (const param of params) {
146
- let dynamicPath = route.pathname;
147
- for (const segment of route.dynamicSegments) {
148
- const value = param[segment];
149
- if (value === void 0) {
150
- if (debug) {
151
- console.warn(`[next-sitemap] ${route.pathname}: missing param "${segment}" in`, param);
189
+ const module = await importPage(appDirectory, route.key);
190
+ getParams = module.generateStaticParams || null;
191
+ } catch (error) {
192
+ if (debug) {
193
+ console.warn(`[next-sitemap] ${route.pathname}: import failed:`, error);
194
+ }
195
+ }
196
+ if (getParams) {
197
+ try {
198
+ const params = await getParams();
199
+ if (debug) {
200
+ console.log(`[next-sitemap] ${route.pathname}: generateStaticParams returned ${params.length} params`);
201
+ }
202
+ for (const param of params) {
203
+ let dynamicPath = route.pathname;
204
+ for (const segment of route.dynamicSegments) {
205
+ const value = param[segment];
206
+ if (value === void 0) {
207
+ if (debug) {
208
+ console.warn(`[next-sitemap] ${route.pathname}: missing param "${segment}" in`, param);
209
+ }
210
+ continue;
152
211
  }
153
- continue;
212
+ dynamicPath = dynamicPath.replace(`[${segment}]`, value);
213
+ }
214
+ if (!seenPaths.has(dynamicPath)) {
215
+ allPaths.push(dynamicPath);
216
+ seenPaths.add(dynamicPath);
154
217
  }
155
- dynamicPath = dynamicPath.replace(`[${segment}]`, value);
156
- }
157
- if (!seenPaths.has(dynamicPath)) {
158
- allPaths.push(dynamicPath);
159
- seenPaths.add(dynamicPath);
160
218
  }
219
+ } catch (error) {
220
+ console.error(`[next-sitemap] Error calling generateStaticParams for ${route.pathname}:`, error);
221
+ }
222
+ } else {
223
+ if (debug) {
224
+ console.warn(
225
+ `[next-sitemap] Skipping dynamic route ${route.pathname}: no generateStaticParams exported. Use additionalSitemaps for routes that fetch data at runtime.`
226
+ );
161
227
  }
162
- } catch (error) {
163
- console.error(`[next-sitemap] Error calling generateStaticParams for ${route.pathname}:`, error);
164
- }
165
- } else if (route.dynamicSegments.length > 0) {
166
- if (debug) {
167
- console.warn(
168
- `[next-sitemap] Skipping dynamic route ${route.pathname}: no generateStaticParams exported. Use additionalSitemaps for routes that fetch data at runtime.`
169
- );
170
228
  }
171
229
  }
172
230
  }
@@ -198,18 +256,20 @@ function pathsToEntries(paths, config) {
198
256
  function createSitemapIndexHandler(options) {
199
257
  const { urlsPerSitemap = 5e3, locales = [], defaultLocale, additionalSitemaps, exclude, debug = false } = options;
200
258
  const localeSegment = options.localeSegment ?? (locales.length > 0 || defaultLocale ? "[locale]" : "");
201
- const routes = extractRoutes(options.pagesContext, localeSegment);
202
- if (debug) {
203
- console.log(`[next-sitemap] Found ${routes.length} routes:`);
204
- routes.forEach((r) => {
205
- const hasParams = r.getParams ? "\u2713 generateStaticParams" : "\u2717 no generateStaticParams";
206
- const segments = r.dynamicSegments.length > 0 ? ` [${r.dynamicSegments.join(", ")}]` : "";
207
- console.log(` ${r.pathname}${segments} - ${hasParams}`);
208
- });
209
- }
259
+ const appDir = resolveAppDirectory(options);
260
+ const pageKeys = getPageKeys(options);
261
+ const routes = extractRoutes(pageKeys, localeSegment);
210
262
  return {
211
263
  GET: async () => {
212
- const allPaths = await getAllPaths(routes, debug);
264
+ if (debug) {
265
+ console.log(`[next-sitemap] Found ${routes.length} routes:`);
266
+ routes.forEach((r) => {
267
+ const isDynamic = r.dynamicSegments.length > 0;
268
+ const segments = isDynamic ? ` [${r.dynamicSegments.join(", ")}]` : "";
269
+ console.log(` ${r.pathname}${segments}${isDynamic ? " (dynamic)" : ""}`);
270
+ });
271
+ }
272
+ const allPaths = await getAllPaths(routes, appDir, debug);
213
273
  const filteredPaths = allPaths.filter((pathname) => !shouldExclude(pathname, exclude));
214
274
  const sitemapCount = Math.max(1, Math.ceil(filteredPaths.length / urlsPerSitemap));
215
275
  const xml = generateSitemapIndexXml(options.baseUrl, sitemapCount, {
@@ -224,9 +284,11 @@ function createSitemapIndexHandler(options) {
224
284
  function createSitemapHandler(options) {
225
285
  const { urlsPerSitemap = 5e3, locales = [], defaultLocale, exclude, debug = false } = options;
226
286
  const localeSegment = options.localeSegment ?? (locales.length > 0 || defaultLocale ? "[locale]" : "");
227
- const routes = extractRoutes(options.pagesContext, localeSegment);
287
+ const appDir = resolveAppDirectory(options);
288
+ const pageKeys = getPageKeys(options);
289
+ const routes = extractRoutes(pageKeys, localeSegment);
228
290
  const getFilteredPaths = async () => {
229
- const allPaths = await getAllPaths(routes, debug);
291
+ const allPaths = await getAllPaths(routes, appDir, debug);
230
292
  return allPaths.filter((pathname) => !shouldExclude(pathname, exclude));
231
293
  };
232
294
  return {
package/dist/index.d.cts CHANGED
@@ -16,11 +16,6 @@ interface SitemapConfig {
16
16
  * Number of URLs per sitemap file (default: 5000)
17
17
  */
18
18
  urlsPerSitemap?: number;
19
- /**
20
- * Path to the app directory relative to the project root
21
- * Default: "./src/app" or "./app"
22
- */
23
- appDir?: string;
24
19
  /**
25
20
  * Locale segment in the URL path (e.g., "[locale]")
26
21
  * Auto-detected as "[locale]" when locales or defaultLocale is set
package/dist/index.d.ts CHANGED
@@ -16,11 +16,6 @@ interface SitemapConfig {
16
16
  * Number of URLs per sitemap file (default: 5000)
17
17
  */
18
18
  urlsPerSitemap?: number;
19
- /**
20
- * Path to the app directory relative to the project root
21
- * Default: "./src/app" or "./app"
22
- */
23
- appDir?: string;
24
19
  /**
25
20
  * Locale segment in the URL path (e.g., "[locale]")
26
21
  * Auto-detected as "[locale]" when locales or defaultLocale is set
@@ -1,5 +1,33 @@
1
1
  'use strict';
2
2
 
3
+ var fs = require('fs');
4
+ var path = require('path');
5
+ var jiti$1 = require('jiti');
6
+
7
+ var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
8
+ function _interopNamespace(e) {
9
+ if (e && e.__esModule) return e;
10
+ var n = Object.create(null);
11
+ if (e) {
12
+ Object.keys(e).forEach(function (k) {
13
+ if (k !== 'default') {
14
+ var d = Object.getOwnPropertyDescriptor(e, k);
15
+ Object.defineProperty(n, k, d.get ? d : {
16
+ enumerable: true,
17
+ get: function () { return e[k]; }
18
+ });
19
+ }
20
+ });
21
+ }
22
+ n.default = e;
23
+ return Object.freeze(n);
24
+ }
25
+
26
+ var fs__namespace = /*#__PURE__*/_interopNamespace(fs);
27
+ var path__namespace = /*#__PURE__*/_interopNamespace(path);
28
+
29
+ // src/pages/index.ts
30
+
3
31
  // src/index.ts
4
32
  function calculateDepthPriority(pathname) {
5
33
  if (pathname === "/") return 1;
@@ -139,13 +167,50 @@ function generateRobotsTxt(config) {
139
167
  }
140
168
 
141
169
  // src/pages/index.ts
142
- function extractRoutes(pagesContext, localeSegment) {
170
+ function findPageFiles(dir, baseDir = dir) {
171
+ const files = [];
172
+ try {
173
+ const entries = fs__namespace.readdirSync(dir, { withFileTypes: true });
174
+ for (const entry of entries) {
175
+ const fullPath = path__namespace.join(dir, entry.name);
176
+ if (entry.isDirectory()) {
177
+ if (entry.name === "api") continue;
178
+ files.push(...findPageFiles(fullPath, baseDir));
179
+ } else if ((entry.name.endsWith(".tsx") || entry.name.endsWith(".ts")) && !entry.name.startsWith("_")) {
180
+ const relativePath = "./" + path__namespace.relative(baseDir, fullPath).replace(/\\/g, "/");
181
+ files.push(relativePath);
182
+ }
183
+ }
184
+ } catch {
185
+ }
186
+ return files;
187
+ }
188
+ function detectPagesDirectory() {
189
+ const srcPages = path__namespace.join(process.cwd(), "src/pages");
190
+ if (fs__namespace.existsSync(srcPages)) {
191
+ return srcPages;
192
+ }
193
+ return path__namespace.join(process.cwd(), "pages");
194
+ }
195
+ function resolvePagesDirectory(options) {
196
+ if (options.pagesDirectory) {
197
+ return path__namespace.isAbsolute(options.pagesDirectory) ? options.pagesDirectory : path__namespace.join(process.cwd(), options.pagesDirectory);
198
+ }
199
+ return detectPagesDirectory();
200
+ }
201
+ function getPageKeys(options) {
202
+ return findPageFiles(resolvePagesDirectory(options));
203
+ }
204
+ var jiti = jiti$1.createJiti((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href)), { jsx: true });
205
+ async function importPage(pagesDirectory, key) {
206
+ const relativePath = key.replace("./", "");
207
+ const absolutePath = path__namespace.join(pagesDirectory, relativePath);
208
+ return jiti.import(absolutePath);
209
+ }
210
+ function extractRoutes(pageKeys, localeSegment) {
143
211
  const routes = [];
144
- for (const key of pagesContext.keys()) {
212
+ for (const key of pageKeys) {
145
213
  if (key.includes("[...")) continue;
146
- if (key.includes("/api/")) continue;
147
- if (key.includes("/_")) continue;
148
- const pageModule = pagesContext(key);
149
214
  let pathname = key.replace(/^\.\//, "/").replace(/\.tsx?$/, "").replace(/\/index$/, "/");
150
215
  if (pathname === "") pathname = "/";
151
216
  if (localeSegment) {
@@ -161,25 +226,15 @@ function extractRoutes(pagesContext, localeSegment) {
161
226
  pathname = "/" + pathname;
162
227
  }
163
228
  const dynamicSegments = pathname.match(/\[([^\]]+)\]/g)?.map((s) => s.slice(1, -1)) || [];
164
- const getStaticPaths = pageModule.getStaticPaths;
165
- let getParams = null;
166
- if (getStaticPaths) {
167
- getParams = async () => {
168
- const result = await getStaticPaths();
169
- return result.paths.map((p) => p.params);
170
- };
171
- } else if (pageModule.generateStaticParams) {
172
- getParams = pageModule.generateStaticParams;
173
- }
174
229
  routes.push({
175
230
  pathname,
176
231
  dynamicSegments,
177
- getParams
232
+ key
178
233
  });
179
234
  }
180
235
  return routes;
181
236
  }
182
- async function getAllPaths(routes, debug = false) {
237
+ async function getAllPaths(routes, pagesDirectory, debug = false) {
183
238
  const allPaths = ["/"];
184
239
  const seenPaths = /* @__PURE__ */ new Set(["/"]);
185
240
  for (const route of routes) {
@@ -188,37 +243,59 @@ async function getAllPaths(routes, debug = false) {
188
243
  allPaths.push(route.pathname);
189
244
  seenPaths.add(route.pathname);
190
245
  }
191
- } else if (route.getParams) {
246
+ } else {
247
+ let getParams = null;
192
248
  try {
193
- const params = await route.getParams();
194
249
  if (debug) {
195
- console.log(`[next-sitemap] ${route.pathname}: getStaticPaths returned ${params.length} params`);
250
+ console.log(`[next-sitemap] ${route.pathname}: importing ${route.key}`);
251
+ }
252
+ const module = await importPage(pagesDirectory, route.key);
253
+ const getStaticPaths = module.getStaticPaths;
254
+ if (getStaticPaths) {
255
+ getParams = async () => {
256
+ const result = await getStaticPaths();
257
+ return result.paths.map((p) => p.params);
258
+ };
259
+ } else if (module.generateStaticParams) {
260
+ getParams = module.generateStaticParams;
261
+ }
262
+ } catch (error) {
263
+ if (debug) {
264
+ console.warn(`[next-sitemap] ${route.pathname}: import failed:`, error);
196
265
  }
197
- for (const param of params) {
198
- let dynamicPath = route.pathname;
199
- for (const segment of route.dynamicSegments) {
200
- const value = param[segment];
201
- if (value === void 0) {
202
- if (debug) {
203
- console.warn(`[next-sitemap] ${route.pathname}: missing param "${segment}" in`, param);
266
+ }
267
+ if (getParams) {
268
+ try {
269
+ const params = await getParams();
270
+ if (debug) {
271
+ console.log(`[next-sitemap] ${route.pathname}: getStaticPaths returned ${params.length} params`);
272
+ }
273
+ for (const param of params) {
274
+ let dynamicPath = route.pathname;
275
+ for (const segment of route.dynamicSegments) {
276
+ const value = param[segment];
277
+ if (value === void 0) {
278
+ if (debug) {
279
+ console.warn(`[next-sitemap] ${route.pathname}: missing param "${segment}" in`, param);
280
+ }
281
+ continue;
204
282
  }
205
- continue;
283
+ dynamicPath = dynamicPath.replace(`[${segment}]`, value);
284
+ }
285
+ if (!seenPaths.has(dynamicPath)) {
286
+ allPaths.push(dynamicPath);
287
+ seenPaths.add(dynamicPath);
206
288
  }
207
- dynamicPath = dynamicPath.replace(`[${segment}]`, value);
208
- }
209
- if (!seenPaths.has(dynamicPath)) {
210
- allPaths.push(dynamicPath);
211
- seenPaths.add(dynamicPath);
212
289
  }
290
+ } catch (error) {
291
+ console.error(`[next-sitemap] Error calling getStaticPaths for ${route.pathname}:`, error);
292
+ }
293
+ } else {
294
+ if (debug) {
295
+ console.warn(
296
+ `[next-sitemap] Skipping dynamic route ${route.pathname}: no getStaticPaths exported. Use additionalSitemaps for routes that fetch data at runtime.`
297
+ );
213
298
  }
214
- } catch (error) {
215
- console.error(`[next-sitemap] Error calling getStaticPaths for ${route.pathname}:`, error);
216
- }
217
- } else if (route.dynamicSegments.length > 0) {
218
- if (debug) {
219
- console.warn(
220
- `[next-sitemap] Skipping dynamic route ${route.pathname}: no getStaticPaths exported. Use additionalSitemaps for routes that fetch data at runtime.`
221
- );
222
299
  }
223
300
  }
224
301
  }
@@ -250,17 +327,19 @@ function pathsToEntries(paths, config) {
250
327
  function createSitemapIndexApiHandler(options) {
251
328
  const { urlsPerSitemap = 5e3, additionalSitemaps, exclude, debug = false } = options;
252
329
  const localeSegment = options.localeSegment ?? "";
253
- const routes = extractRoutes(options.pagesContext, localeSegment);
330
+ const pagesDir = resolvePagesDirectory(options);
331
+ const pageKeys = getPageKeys(options);
332
+ const routes = extractRoutes(pageKeys, localeSegment);
254
333
  if (debug) {
255
334
  console.log(`[next-sitemap] Found ${routes.length} routes:`);
256
335
  routes.forEach((r) => {
257
- const hasParams = r.getParams ? "\u2713 getStaticPaths" : "\u2717 no getStaticPaths";
258
- const segments = r.dynamicSegments.length > 0 ? ` [${r.dynamicSegments.join(", ")}]` : "";
259
- console.log(` ${r.pathname}${segments} - ${hasParams}`);
336
+ const isDynamic = r.dynamicSegments.length > 0;
337
+ const segments = isDynamic ? ` [${r.dynamicSegments.join(", ")}]` : "";
338
+ console.log(` ${r.pathname}${segments}${isDynamic ? " (dynamic)" : ""}`);
260
339
  });
261
340
  }
262
341
  return async function handler(_req, res) {
263
- const allPaths = await getAllPaths(routes, debug);
342
+ const allPaths = await getAllPaths(routes, pagesDir, debug);
264
343
  const filteredPaths = allPaths.filter((pathname) => !shouldExclude(pathname, exclude));
265
344
  const sitemapCount = Math.max(1, Math.ceil(filteredPaths.length / urlsPerSitemap));
266
345
  const xml = generateSitemapIndexXml(options.baseUrl, sitemapCount, {
@@ -273,9 +352,11 @@ function createSitemapIndexApiHandler(options) {
273
352
  function createSitemapApiHandler(options) {
274
353
  const { urlsPerSitemap = 5e3, exclude, debug = false } = options;
275
354
  const localeSegment = options.localeSegment ?? "";
276
- const routes = extractRoutes(options.pagesContext, localeSegment);
355
+ const pagesDir = resolvePagesDirectory(options);
356
+ const pageKeys = getPageKeys(options);
357
+ const routes = extractRoutes(pageKeys, localeSegment);
277
358
  const getFilteredPaths = async () => {
278
- const allPaths = await getAllPaths(routes, debug);
359
+ const allPaths = await getAllPaths(routes, pagesDir, debug);
279
360
  return allPaths.filter((pathname) => !shouldExclude(pathname, exclude));
280
361
  };
281
362
  return async function handler(req, res) {
@@ -294,8 +375,10 @@ function createSitemapApiHandler(options) {
294
375
  async function getSitemapStaticPaths(options) {
295
376
  const { urlsPerSitemap = 5e3, exclude, debug = false } = options;
296
377
  const localeSegment = options.localeSegment ?? "";
297
- const routes = extractRoutes(options.pagesContext, localeSegment);
298
- const allPaths = await getAllPaths(routes, debug);
378
+ const pagesDir = resolvePagesDirectory(options);
379
+ const pageKeys = getPageKeys(options);
380
+ const routes = extractRoutes(pageKeys, localeSegment);
381
+ const allPaths = await getAllPaths(routes, pagesDir, debug);
299
382
  const filteredPaths = allPaths.filter((pathname) => !shouldExclude(pathname, exclude));
300
383
  const sitemapCount = Math.max(1, Math.ceil(filteredPaths.length / urlsPerSitemap));
301
384
  return {
@@ -19,11 +19,6 @@ interface SitemapConfig {
19
19
  * Number of URLs per sitemap file (default: 5000)
20
20
  */
21
21
  urlsPerSitemap?: number;
22
- /**
23
- * Path to the app directory relative to the project root
24
- * Default: "./src/app" or "./app"
25
- */
26
- appDir?: string;
27
22
  /**
28
23
  * Locale segment in the URL path (e.g., "[locale]")
29
24
  * Auto-detected as "[locale]" when locales or defaultLocale is set
@@ -69,9 +64,6 @@ interface SitemapEntry {
69
64
  languages?: Record<string, string>;
70
65
  };
71
66
  }
72
- type PageModule = {
73
- generateStaticParams?: () => Promise<Record<string, string>[]>;
74
- };
75
67
 
76
68
  /**
77
69
  * Generate robots.txt content from configuration
@@ -81,13 +73,16 @@ declare function generateRobotsTxt(config: MetadataRoute.Robots): string;
81
73
 
82
74
  interface CreateSitemapApiHandlerOptions extends SitemapConfig {
83
75
  /**
84
- * The require.context result for page discovery
85
- * Example: require.context('./', true, /^\.\/(?!\[|_|api\/).*\.tsx$/)
76
+ * Path to the pages directory to scan for page files.
77
+ * Can be absolute or relative to process.cwd().
78
+ * If not provided, auto-detects src/pages or pages.
79
+ *
80
+ * Example:
81
+ * ```ts
82
+ * pagesDirectory: 'src/pages'
83
+ * ```
86
84
  */
87
- pagesContext: {
88
- keys: () => string[];
89
- (key: string): PageModule;
90
- };
85
+ pagesDirectory?: string;
91
86
  /**
92
87
  * Enable debug logging to diagnose issues with route discovery
93
88
  * Logs info about getStaticPaths calls and skipped routes
@@ -19,11 +19,6 @@ interface SitemapConfig {
19
19
  * Number of URLs per sitemap file (default: 5000)
20
20
  */
21
21
  urlsPerSitemap?: number;
22
- /**
23
- * Path to the app directory relative to the project root
24
- * Default: "./src/app" or "./app"
25
- */
26
- appDir?: string;
27
22
  /**
28
23
  * Locale segment in the URL path (e.g., "[locale]")
29
24
  * Auto-detected as "[locale]" when locales or defaultLocale is set
@@ -69,9 +64,6 @@ interface SitemapEntry {
69
64
  languages?: Record<string, string>;
70
65
  };
71
66
  }
72
- type PageModule = {
73
- generateStaticParams?: () => Promise<Record<string, string>[]>;
74
- };
75
67
 
76
68
  /**
77
69
  * Generate robots.txt content from configuration
@@ -81,13 +73,16 @@ declare function generateRobotsTxt(config: MetadataRoute.Robots): string;
81
73
 
82
74
  interface CreateSitemapApiHandlerOptions extends SitemapConfig {
83
75
  /**
84
- * The require.context result for page discovery
85
- * Example: require.context('./', true, /^\.\/(?!\[|_|api\/).*\.tsx$/)
76
+ * Path to the pages directory to scan for page files.
77
+ * Can be absolute or relative to process.cwd().
78
+ * If not provided, auto-detects src/pages or pages.
79
+ *
80
+ * Example:
81
+ * ```ts
82
+ * pagesDirectory: 'src/pages'
83
+ * ```
86
84
  */
87
- pagesContext: {
88
- keys: () => string[];
89
- (key: string): PageModule;
90
- };
85
+ pagesDirectory?: string;
91
86
  /**
92
87
  * Enable debug logging to diagnose issues with route discovery
93
88
  * Logs info about getStaticPaths calls and skipped routes
@@ -1,3 +1,9 @@
1
+ import * as fs from 'fs';
2
+ import * as path from 'path';
3
+ import { createJiti } from 'jiti';
4
+
5
+ // src/pages/index.ts
6
+
1
7
  // src/index.ts
2
8
  function calculateDepthPriority(pathname) {
3
9
  if (pathname === "/") return 1;
@@ -137,13 +143,50 @@ function generateRobotsTxt(config) {
137
143
  }
138
144
 
139
145
  // src/pages/index.ts
140
- function extractRoutes(pagesContext, localeSegment) {
146
+ function findPageFiles(dir, baseDir = dir) {
147
+ const files = [];
148
+ try {
149
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
150
+ for (const entry of entries) {
151
+ const fullPath = path.join(dir, entry.name);
152
+ if (entry.isDirectory()) {
153
+ if (entry.name === "api") continue;
154
+ files.push(...findPageFiles(fullPath, baseDir));
155
+ } else if ((entry.name.endsWith(".tsx") || entry.name.endsWith(".ts")) && !entry.name.startsWith("_")) {
156
+ const relativePath = "./" + path.relative(baseDir, fullPath).replace(/\\/g, "/");
157
+ files.push(relativePath);
158
+ }
159
+ }
160
+ } catch {
161
+ }
162
+ return files;
163
+ }
164
+ function detectPagesDirectory() {
165
+ const srcPages = path.join(process.cwd(), "src/pages");
166
+ if (fs.existsSync(srcPages)) {
167
+ return srcPages;
168
+ }
169
+ return path.join(process.cwd(), "pages");
170
+ }
171
+ function resolvePagesDirectory(options) {
172
+ if (options.pagesDirectory) {
173
+ return path.isAbsolute(options.pagesDirectory) ? options.pagesDirectory : path.join(process.cwd(), options.pagesDirectory);
174
+ }
175
+ return detectPagesDirectory();
176
+ }
177
+ function getPageKeys(options) {
178
+ return findPageFiles(resolvePagesDirectory(options));
179
+ }
180
+ var jiti = createJiti(import.meta.url, { jsx: true });
181
+ async function importPage(pagesDirectory, key) {
182
+ const relativePath = key.replace("./", "");
183
+ const absolutePath = path.join(pagesDirectory, relativePath);
184
+ return jiti.import(absolutePath);
185
+ }
186
+ function extractRoutes(pageKeys, localeSegment) {
141
187
  const routes = [];
142
- for (const key of pagesContext.keys()) {
188
+ for (const key of pageKeys) {
143
189
  if (key.includes("[...")) continue;
144
- if (key.includes("/api/")) continue;
145
- if (key.includes("/_")) continue;
146
- const pageModule = pagesContext(key);
147
190
  let pathname = key.replace(/^\.\//, "/").replace(/\.tsx?$/, "").replace(/\/index$/, "/");
148
191
  if (pathname === "") pathname = "/";
149
192
  if (localeSegment) {
@@ -159,25 +202,15 @@ function extractRoutes(pagesContext, localeSegment) {
159
202
  pathname = "/" + pathname;
160
203
  }
161
204
  const dynamicSegments = pathname.match(/\[([^\]]+)\]/g)?.map((s) => s.slice(1, -1)) || [];
162
- const getStaticPaths = pageModule.getStaticPaths;
163
- let getParams = null;
164
- if (getStaticPaths) {
165
- getParams = async () => {
166
- const result = await getStaticPaths();
167
- return result.paths.map((p) => p.params);
168
- };
169
- } else if (pageModule.generateStaticParams) {
170
- getParams = pageModule.generateStaticParams;
171
- }
172
205
  routes.push({
173
206
  pathname,
174
207
  dynamicSegments,
175
- getParams
208
+ key
176
209
  });
177
210
  }
178
211
  return routes;
179
212
  }
180
- async function getAllPaths(routes, debug = false) {
213
+ async function getAllPaths(routes, pagesDirectory, debug = false) {
181
214
  const allPaths = ["/"];
182
215
  const seenPaths = /* @__PURE__ */ new Set(["/"]);
183
216
  for (const route of routes) {
@@ -186,37 +219,59 @@ async function getAllPaths(routes, debug = false) {
186
219
  allPaths.push(route.pathname);
187
220
  seenPaths.add(route.pathname);
188
221
  }
189
- } else if (route.getParams) {
222
+ } else {
223
+ let getParams = null;
190
224
  try {
191
- const params = await route.getParams();
192
225
  if (debug) {
193
- console.log(`[next-sitemap] ${route.pathname}: getStaticPaths returned ${params.length} params`);
226
+ console.log(`[next-sitemap] ${route.pathname}: importing ${route.key}`);
227
+ }
228
+ const module = await importPage(pagesDirectory, route.key);
229
+ const getStaticPaths = module.getStaticPaths;
230
+ if (getStaticPaths) {
231
+ getParams = async () => {
232
+ const result = await getStaticPaths();
233
+ return result.paths.map((p) => p.params);
234
+ };
235
+ } else if (module.generateStaticParams) {
236
+ getParams = module.generateStaticParams;
194
237
  }
195
- for (const param of params) {
196
- let dynamicPath = route.pathname;
197
- for (const segment of route.dynamicSegments) {
198
- const value = param[segment];
199
- if (value === void 0) {
200
- if (debug) {
201
- console.warn(`[next-sitemap] ${route.pathname}: missing param "${segment}" in`, param);
238
+ } catch (error) {
239
+ if (debug) {
240
+ console.warn(`[next-sitemap] ${route.pathname}: import failed:`, error);
241
+ }
242
+ }
243
+ if (getParams) {
244
+ try {
245
+ const params = await getParams();
246
+ if (debug) {
247
+ console.log(`[next-sitemap] ${route.pathname}: getStaticPaths returned ${params.length} params`);
248
+ }
249
+ for (const param of params) {
250
+ let dynamicPath = route.pathname;
251
+ for (const segment of route.dynamicSegments) {
252
+ const value = param[segment];
253
+ if (value === void 0) {
254
+ if (debug) {
255
+ console.warn(`[next-sitemap] ${route.pathname}: missing param "${segment}" in`, param);
256
+ }
257
+ continue;
202
258
  }
203
- continue;
259
+ dynamicPath = dynamicPath.replace(`[${segment}]`, value);
260
+ }
261
+ if (!seenPaths.has(dynamicPath)) {
262
+ allPaths.push(dynamicPath);
263
+ seenPaths.add(dynamicPath);
204
264
  }
205
- dynamicPath = dynamicPath.replace(`[${segment}]`, value);
206
- }
207
- if (!seenPaths.has(dynamicPath)) {
208
- allPaths.push(dynamicPath);
209
- seenPaths.add(dynamicPath);
210
265
  }
266
+ } catch (error) {
267
+ console.error(`[next-sitemap] Error calling getStaticPaths for ${route.pathname}:`, error);
268
+ }
269
+ } else {
270
+ if (debug) {
271
+ console.warn(
272
+ `[next-sitemap] Skipping dynamic route ${route.pathname}: no getStaticPaths exported. Use additionalSitemaps for routes that fetch data at runtime.`
273
+ );
211
274
  }
212
- } catch (error) {
213
- console.error(`[next-sitemap] Error calling getStaticPaths for ${route.pathname}:`, error);
214
- }
215
- } else if (route.dynamicSegments.length > 0) {
216
- if (debug) {
217
- console.warn(
218
- `[next-sitemap] Skipping dynamic route ${route.pathname}: no getStaticPaths exported. Use additionalSitemaps for routes that fetch data at runtime.`
219
- );
220
275
  }
221
276
  }
222
277
  }
@@ -248,17 +303,19 @@ function pathsToEntries(paths, config) {
248
303
  function createSitemapIndexApiHandler(options) {
249
304
  const { urlsPerSitemap = 5e3, additionalSitemaps, exclude, debug = false } = options;
250
305
  const localeSegment = options.localeSegment ?? "";
251
- const routes = extractRoutes(options.pagesContext, localeSegment);
306
+ const pagesDir = resolvePagesDirectory(options);
307
+ const pageKeys = getPageKeys(options);
308
+ const routes = extractRoutes(pageKeys, localeSegment);
252
309
  if (debug) {
253
310
  console.log(`[next-sitemap] Found ${routes.length} routes:`);
254
311
  routes.forEach((r) => {
255
- const hasParams = r.getParams ? "\u2713 getStaticPaths" : "\u2717 no getStaticPaths";
256
- const segments = r.dynamicSegments.length > 0 ? ` [${r.dynamicSegments.join(", ")}]` : "";
257
- console.log(` ${r.pathname}${segments} - ${hasParams}`);
312
+ const isDynamic = r.dynamicSegments.length > 0;
313
+ const segments = isDynamic ? ` [${r.dynamicSegments.join(", ")}]` : "";
314
+ console.log(` ${r.pathname}${segments}${isDynamic ? " (dynamic)" : ""}`);
258
315
  });
259
316
  }
260
317
  return async function handler(_req, res) {
261
- const allPaths = await getAllPaths(routes, debug);
318
+ const allPaths = await getAllPaths(routes, pagesDir, debug);
262
319
  const filteredPaths = allPaths.filter((pathname) => !shouldExclude(pathname, exclude));
263
320
  const sitemapCount = Math.max(1, Math.ceil(filteredPaths.length / urlsPerSitemap));
264
321
  const xml = generateSitemapIndexXml(options.baseUrl, sitemapCount, {
@@ -271,9 +328,11 @@ function createSitemapIndexApiHandler(options) {
271
328
  function createSitemapApiHandler(options) {
272
329
  const { urlsPerSitemap = 5e3, exclude, debug = false } = options;
273
330
  const localeSegment = options.localeSegment ?? "";
274
- const routes = extractRoutes(options.pagesContext, localeSegment);
331
+ const pagesDir = resolvePagesDirectory(options);
332
+ const pageKeys = getPageKeys(options);
333
+ const routes = extractRoutes(pageKeys, localeSegment);
275
334
  const getFilteredPaths = async () => {
276
- const allPaths = await getAllPaths(routes, debug);
335
+ const allPaths = await getAllPaths(routes, pagesDir, debug);
277
336
  return allPaths.filter((pathname) => !shouldExclude(pathname, exclude));
278
337
  };
279
338
  return async function handler(req, res) {
@@ -292,8 +351,10 @@ function createSitemapApiHandler(options) {
292
351
  async function getSitemapStaticPaths(options) {
293
352
  const { urlsPerSitemap = 5e3, exclude, debug = false } = options;
294
353
  const localeSegment = options.localeSegment ?? "";
295
- const routes = extractRoutes(options.pagesContext, localeSegment);
296
- const allPaths = await getAllPaths(routes, debug);
354
+ const pagesDir = resolvePagesDirectory(options);
355
+ const pageKeys = getPageKeys(options);
356
+ const routes = extractRoutes(pageKeys, localeSegment);
357
+ const allPaths = await getAllPaths(routes, pagesDir, debug);
297
358
  const filteredPaths = allPaths.filter((pathname) => !shouldExclude(pathname, exclude));
298
359
  const sitemapCount = Math.max(1, Math.ceil(filteredPaths.length / urlsPerSitemap));
299
360
  return {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@onruntime/next-sitemap",
3
- "version": "0.5.0",
3
+ "version": "0.6.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": {
@@ -38,11 +38,15 @@
38
38
  "files": [
39
39
  "dist"
40
40
  ],
41
+ "dependencies": {
42
+ "jiti": "^2.6.1"
43
+ },
41
44
  "peerDependencies": {
42
45
  "next": ">=14.0.0"
43
46
  },
44
47
  "devDependencies": {
45
48
  "@types/node": "^22.0.0",
49
+ "@types/react": "^19.2.7",
46
50
  "next": "^16.1.1",
47
51
  "tsup": "^8",
48
52
  "tsx": "^4",