@onruntime/next-sitemap 0.4.0 → 0.5.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 +42 -0
- package/dist/app/index.cjs +9 -3
- package/dist/app/index.js +9 -3
- package/dist/index.cjs +10 -3
- package/dist/index.d.cts +5 -1
- package/dist/index.d.ts +5 -1
- package/dist/index.js +10 -4
- package/dist/pages/index.cjs +56 -3
- package/dist/pages/index.d.cts +27 -2
- package/dist/pages/index.d.ts +27 -2
- package/dist/pages/index.js +55 -4
- package/package.json +4 -2
package/README.md
CHANGED
|
@@ -17,6 +17,7 @@ Dynamic sitemap generation for Next.js with automatic route discovery.
|
|
|
17
17
|
- Multi-sitemap support with sitemap index (for sites with >50,000 URLs)
|
|
18
18
|
- hreflang alternates for i18n
|
|
19
19
|
- Fully static generation (SSG)
|
|
20
|
+
- robots.txt generation with Next.js `MetadataRoute.Robots` compatibility
|
|
20
21
|
|
|
21
22
|
## Installation
|
|
22
23
|
|
|
@@ -354,6 +355,47 @@ export async function GET() {
|
|
|
354
355
|
</urlset>
|
|
355
356
|
```
|
|
356
357
|
|
|
358
|
+
## Robots.txt (Pages Router)
|
|
359
|
+
|
|
360
|
+
For Pages Router, use `createRobotsApiHandler` to generate a `robots.txt` file. The configuration uses the same format as [Next.js `MetadataRoute.Robots`](https://nextjs.org/docs/app/api-reference/file-conventions/metadata/robots).
|
|
361
|
+
|
|
362
|
+
```typescript
|
|
363
|
+
// pages/api/robots.txt.ts
|
|
364
|
+
import { createRobotsApiHandler } from "@onruntime/next-sitemap/pages";
|
|
365
|
+
|
|
366
|
+
export default createRobotsApiHandler({
|
|
367
|
+
rules: {
|
|
368
|
+
userAgent: "*",
|
|
369
|
+
allow: "/",
|
|
370
|
+
disallow: ["/admin", "/private"],
|
|
371
|
+
},
|
|
372
|
+
sitemap: "https://example.com/sitemap.xml",
|
|
373
|
+
});
|
|
374
|
+
```
|
|
375
|
+
|
|
376
|
+
Add a rewrite in `next.config.ts`:
|
|
377
|
+
|
|
378
|
+
```typescript
|
|
379
|
+
{
|
|
380
|
+
source: "/robots.txt",
|
|
381
|
+
destination: "/api/robots.txt",
|
|
382
|
+
}
|
|
383
|
+
```
|
|
384
|
+
|
|
385
|
+
**Note:** For App Router, use the [native `robots.ts` file convention](https://nextjs.org/docs/app/api-reference/file-conventions/metadata/robots) instead.
|
|
386
|
+
|
|
387
|
+
## Requirements
|
|
388
|
+
|
|
389
|
+
This package requires a **Node.js server runtime**. It works with:
|
|
390
|
+
- Vercel
|
|
391
|
+
- Netlify (with Next.js runtime)
|
|
392
|
+
- Railway, Render, Fly.io
|
|
393
|
+
- AWS (EC2, ECS, Lambda)
|
|
394
|
+
- Docker containers
|
|
395
|
+
- Any Node.js server
|
|
396
|
+
|
|
397
|
+
It does **not** work with static exports (`output: 'export'`) or static hosting platforms like Cloudflare Pages, Netlify (static mode), or GitHub Pages.
|
|
398
|
+
|
|
357
399
|
## Troubleshooting
|
|
358
400
|
|
|
359
401
|
### Dynamic routes not included in sitemap
|
package/dist/app/index.cjs
CHANGED
|
@@ -4,7 +4,8 @@
|
|
|
4
4
|
function calculateDepthPriority(pathname) {
|
|
5
5
|
if (pathname === "/") return 1;
|
|
6
6
|
const depth = pathname.split("/").filter(Boolean).length;
|
|
7
|
-
|
|
7
|
+
const priority = Math.max(0.1, 1 - depth * 0.2);
|
|
8
|
+
return Math.round(priority * 100) / 100;
|
|
8
9
|
}
|
|
9
10
|
function shouldExclude(pathname, exclude) {
|
|
10
11
|
if (!exclude) return false;
|
|
@@ -35,11 +36,16 @@ function getChangeFreq(pathname, changeFreq) {
|
|
|
35
36
|
}
|
|
36
37
|
return changeFreq;
|
|
37
38
|
}
|
|
39
|
+
function normalizePath(pathname) {
|
|
40
|
+
if (pathname === "/") return pathname;
|
|
41
|
+
return pathname.endsWith("/") ? pathname.slice(0, -1) : pathname;
|
|
42
|
+
}
|
|
38
43
|
function buildUrl(baseUrl, pathname, locale, defaultLocale) {
|
|
44
|
+
const normalizedPath = normalizePath(pathname);
|
|
39
45
|
if (!locale || locale === defaultLocale) {
|
|
40
|
-
return `${baseUrl}${
|
|
46
|
+
return `${baseUrl}${normalizedPath}`;
|
|
41
47
|
}
|
|
42
|
-
return `${baseUrl}/${locale}${
|
|
48
|
+
return `${baseUrl}/${locale}${normalizedPath}`;
|
|
43
49
|
}
|
|
44
50
|
function generateSitemapXml(entries) {
|
|
45
51
|
const hasAlternates = entries.some((e) => e.alternates?.languages);
|
package/dist/app/index.js
CHANGED
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
function calculateDepthPriority(pathname) {
|
|
3
3
|
if (pathname === "/") return 1;
|
|
4
4
|
const depth = pathname.split("/").filter(Boolean).length;
|
|
5
|
-
|
|
5
|
+
const priority = Math.max(0.1, 1 - depth * 0.2);
|
|
6
|
+
return Math.round(priority * 100) / 100;
|
|
6
7
|
}
|
|
7
8
|
function shouldExclude(pathname, exclude) {
|
|
8
9
|
if (!exclude) return false;
|
|
@@ -33,11 +34,16 @@ function getChangeFreq(pathname, changeFreq) {
|
|
|
33
34
|
}
|
|
34
35
|
return changeFreq;
|
|
35
36
|
}
|
|
37
|
+
function normalizePath(pathname) {
|
|
38
|
+
if (pathname === "/") return pathname;
|
|
39
|
+
return pathname.endsWith("/") ? pathname.slice(0, -1) : pathname;
|
|
40
|
+
}
|
|
36
41
|
function buildUrl(baseUrl, pathname, locale, defaultLocale) {
|
|
42
|
+
const normalizedPath = normalizePath(pathname);
|
|
37
43
|
if (!locale || locale === defaultLocale) {
|
|
38
|
-
return `${baseUrl}${
|
|
44
|
+
return `${baseUrl}${normalizedPath}`;
|
|
39
45
|
}
|
|
40
|
-
return `${baseUrl}/${locale}${
|
|
46
|
+
return `${baseUrl}/${locale}${normalizedPath}`;
|
|
41
47
|
}
|
|
42
48
|
function generateSitemapXml(entries) {
|
|
43
49
|
const hasAlternates = entries.some((e) => e.alternates?.languages);
|
package/dist/index.cjs
CHANGED
|
@@ -4,7 +4,8 @@
|
|
|
4
4
|
function calculateDepthPriority(pathname) {
|
|
5
5
|
if (pathname === "/") return 1;
|
|
6
6
|
const depth = pathname.split("/").filter(Boolean).length;
|
|
7
|
-
|
|
7
|
+
const priority = Math.max(0.1, 1 - depth * 0.2);
|
|
8
|
+
return Math.round(priority * 100) / 100;
|
|
8
9
|
}
|
|
9
10
|
function shouldExclude(pathname, exclude) {
|
|
10
11
|
if (!exclude) return false;
|
|
@@ -35,11 +36,16 @@ function getChangeFreq(pathname, changeFreq) {
|
|
|
35
36
|
}
|
|
36
37
|
return changeFreq;
|
|
37
38
|
}
|
|
39
|
+
function normalizePath(pathname) {
|
|
40
|
+
if (pathname === "/") return pathname;
|
|
41
|
+
return pathname.endsWith("/") ? pathname.slice(0, -1) : pathname;
|
|
42
|
+
}
|
|
38
43
|
function buildUrl(baseUrl, pathname, locale, defaultLocale) {
|
|
44
|
+
const normalizedPath = normalizePath(pathname);
|
|
39
45
|
if (!locale || locale === defaultLocale) {
|
|
40
|
-
return `${baseUrl}${
|
|
46
|
+
return `${baseUrl}${normalizedPath}`;
|
|
41
47
|
}
|
|
42
|
-
return `${baseUrl}/${locale}${
|
|
48
|
+
return `${baseUrl}/${locale}${normalizedPath}`;
|
|
43
49
|
}
|
|
44
50
|
function generateSitemapXml(entries) {
|
|
45
51
|
const hasAlternates = entries.some((e) => e.alternates?.languages);
|
|
@@ -100,4 +106,5 @@ exports.generateSitemapIndexXml = generateSitemapIndexXml;
|
|
|
100
106
|
exports.generateSitemapXml = generateSitemapXml;
|
|
101
107
|
exports.getChangeFreq = getChangeFreq;
|
|
102
108
|
exports.getPriority = getPriority;
|
|
109
|
+
exports.normalizePath = normalizePath;
|
|
103
110
|
exports.shouldExclude = shouldExclude;
|
package/dist/index.d.cts
CHANGED
|
@@ -92,6 +92,10 @@ interface RouteInfo {
|
|
|
92
92
|
type PageModule = {
|
|
93
93
|
generateStaticParams?: () => Promise<Record<string, string>[]>;
|
|
94
94
|
};
|
|
95
|
+
/**
|
|
96
|
+
* Normalize pathname by removing trailing slash (except for root)
|
|
97
|
+
*/
|
|
98
|
+
declare function normalizePath(pathname: string): string;
|
|
95
99
|
/**
|
|
96
100
|
* Generate the full URL for a pathname
|
|
97
101
|
*/
|
|
@@ -108,4 +112,4 @@ declare function generateSitemapIndexXml(baseUrl: string, sitemapCount: number,
|
|
|
108
112
|
additionalSitemaps?: string[];
|
|
109
113
|
}): string;
|
|
110
114
|
|
|
111
|
-
export { type ChangeFrequency, type PageModule, type RouteInfo, type SitemapConfig, type SitemapEntry, buildUrl, calculateDepthPriority, generateSitemapIndexXml, generateSitemapXml, getChangeFreq, getPriority, shouldExclude };
|
|
115
|
+
export { type ChangeFrequency, type PageModule, type RouteInfo, type SitemapConfig, type SitemapEntry, buildUrl, calculateDepthPriority, generateSitemapIndexXml, generateSitemapXml, getChangeFreq, getPriority, normalizePath, shouldExclude };
|
package/dist/index.d.ts
CHANGED
|
@@ -92,6 +92,10 @@ interface RouteInfo {
|
|
|
92
92
|
type PageModule = {
|
|
93
93
|
generateStaticParams?: () => Promise<Record<string, string>[]>;
|
|
94
94
|
};
|
|
95
|
+
/**
|
|
96
|
+
* Normalize pathname by removing trailing slash (except for root)
|
|
97
|
+
*/
|
|
98
|
+
declare function normalizePath(pathname: string): string;
|
|
95
99
|
/**
|
|
96
100
|
* Generate the full URL for a pathname
|
|
97
101
|
*/
|
|
@@ -108,4 +112,4 @@ declare function generateSitemapIndexXml(baseUrl: string, sitemapCount: number,
|
|
|
108
112
|
additionalSitemaps?: string[];
|
|
109
113
|
}): string;
|
|
110
114
|
|
|
111
|
-
export { type ChangeFrequency, type PageModule, type RouteInfo, type SitemapConfig, type SitemapEntry, buildUrl, calculateDepthPriority, generateSitemapIndexXml, generateSitemapXml, getChangeFreq, getPriority, shouldExclude };
|
|
115
|
+
export { type ChangeFrequency, type PageModule, type RouteInfo, type SitemapConfig, type SitemapEntry, buildUrl, calculateDepthPriority, generateSitemapIndexXml, generateSitemapXml, getChangeFreq, getPriority, normalizePath, shouldExclude };
|
package/dist/index.js
CHANGED
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
function calculateDepthPriority(pathname) {
|
|
3
3
|
if (pathname === "/") return 1;
|
|
4
4
|
const depth = pathname.split("/").filter(Boolean).length;
|
|
5
|
-
|
|
5
|
+
const priority = Math.max(0.1, 1 - depth * 0.2);
|
|
6
|
+
return Math.round(priority * 100) / 100;
|
|
6
7
|
}
|
|
7
8
|
function shouldExclude(pathname, exclude) {
|
|
8
9
|
if (!exclude) return false;
|
|
@@ -33,11 +34,16 @@ function getChangeFreq(pathname, changeFreq) {
|
|
|
33
34
|
}
|
|
34
35
|
return changeFreq;
|
|
35
36
|
}
|
|
37
|
+
function normalizePath(pathname) {
|
|
38
|
+
if (pathname === "/") return pathname;
|
|
39
|
+
return pathname.endsWith("/") ? pathname.slice(0, -1) : pathname;
|
|
40
|
+
}
|
|
36
41
|
function buildUrl(baseUrl, pathname, locale, defaultLocale) {
|
|
42
|
+
const normalizedPath = normalizePath(pathname);
|
|
37
43
|
if (!locale || locale === defaultLocale) {
|
|
38
|
-
return `${baseUrl}${
|
|
44
|
+
return `${baseUrl}${normalizedPath}`;
|
|
39
45
|
}
|
|
40
|
-
return `${baseUrl}/${locale}${
|
|
46
|
+
return `${baseUrl}/${locale}${normalizedPath}`;
|
|
41
47
|
}
|
|
42
48
|
function generateSitemapXml(entries) {
|
|
43
49
|
const hasAlternates = entries.some((e) => e.alternates?.languages);
|
|
@@ -92,4 +98,4 @@ ${allEntries}
|
|
|
92
98
|
</sitemapindex>`;
|
|
93
99
|
}
|
|
94
100
|
|
|
95
|
-
export { buildUrl, calculateDepthPriority, generateSitemapIndexXml, generateSitemapXml, getChangeFreq, getPriority, shouldExclude };
|
|
101
|
+
export { buildUrl, calculateDepthPriority, generateSitemapIndexXml, generateSitemapXml, getChangeFreq, getPriority, normalizePath, shouldExclude };
|
package/dist/pages/index.cjs
CHANGED
|
@@ -4,7 +4,8 @@
|
|
|
4
4
|
function calculateDepthPriority(pathname) {
|
|
5
5
|
if (pathname === "/") return 1;
|
|
6
6
|
const depth = pathname.split("/").filter(Boolean).length;
|
|
7
|
-
|
|
7
|
+
const priority = Math.max(0.1, 1 - depth * 0.2);
|
|
8
|
+
return Math.round(priority * 100) / 100;
|
|
8
9
|
}
|
|
9
10
|
function shouldExclude(pathname, exclude) {
|
|
10
11
|
if (!exclude) return false;
|
|
@@ -35,11 +36,16 @@ function getChangeFreq(pathname, changeFreq) {
|
|
|
35
36
|
}
|
|
36
37
|
return changeFreq;
|
|
37
38
|
}
|
|
39
|
+
function normalizePath(pathname) {
|
|
40
|
+
if (pathname === "/") return pathname;
|
|
41
|
+
return pathname.endsWith("/") ? pathname.slice(0, -1) : pathname;
|
|
42
|
+
}
|
|
38
43
|
function buildUrl(baseUrl, pathname, locale, defaultLocale) {
|
|
44
|
+
const normalizedPath = normalizePath(pathname);
|
|
39
45
|
if (!locale || locale === defaultLocale) {
|
|
40
|
-
return `${baseUrl}${
|
|
46
|
+
return `${baseUrl}${normalizedPath}`;
|
|
41
47
|
}
|
|
42
|
-
return `${baseUrl}/${locale}${
|
|
48
|
+
return `${baseUrl}/${locale}${normalizedPath}`;
|
|
43
49
|
}
|
|
44
50
|
function generateSitemapXml(entries) {
|
|
45
51
|
const hasAlternates = entries.some((e) => e.alternates?.languages);
|
|
@@ -94,6 +100,44 @@ ${allEntries}
|
|
|
94
100
|
</sitemapindex>`;
|
|
95
101
|
}
|
|
96
102
|
|
|
103
|
+
// src/pages/robots.ts
|
|
104
|
+
function generateRobotsTxt(config) {
|
|
105
|
+
const lines = [];
|
|
106
|
+
const rules = Array.isArray(config.rules) ? config.rules : [config.rules];
|
|
107
|
+
for (const rule of rules) {
|
|
108
|
+
const userAgents = Array.isArray(rule.userAgent) ? rule.userAgent : [rule.userAgent];
|
|
109
|
+
for (const userAgent of userAgents) {
|
|
110
|
+
lines.push(`User-agent: ${userAgent}`);
|
|
111
|
+
}
|
|
112
|
+
if (rule.allow) {
|
|
113
|
+
const allows = Array.isArray(rule.allow) ? rule.allow : [rule.allow];
|
|
114
|
+
for (const allow of allows) {
|
|
115
|
+
lines.push(`Allow: ${allow}`);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
if (rule.disallow) {
|
|
119
|
+
const disallows = Array.isArray(rule.disallow) ? rule.disallow : [rule.disallow];
|
|
120
|
+
for (const disallow of disallows) {
|
|
121
|
+
lines.push(`Disallow: ${disallow}`);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
if (rule.crawlDelay !== void 0) {
|
|
125
|
+
lines.push(`Crawl-delay: ${rule.crawlDelay}`);
|
|
126
|
+
}
|
|
127
|
+
lines.push("");
|
|
128
|
+
}
|
|
129
|
+
if (config.sitemap) {
|
|
130
|
+
const sitemaps = Array.isArray(config.sitemap) ? config.sitemap : [config.sitemap];
|
|
131
|
+
for (const sitemap of sitemaps) {
|
|
132
|
+
lines.push(`Sitemap: ${sitemap}`);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
if (config.host) {
|
|
136
|
+
lines.push(`Host: ${config.host}`);
|
|
137
|
+
}
|
|
138
|
+
return lines.join("\n").trim() + "\n";
|
|
139
|
+
}
|
|
140
|
+
|
|
97
141
|
// src/pages/index.ts
|
|
98
142
|
function extractRoutes(pagesContext, localeSegment) {
|
|
99
143
|
const routes = [];
|
|
@@ -261,7 +305,16 @@ async function getSitemapStaticPaths(options) {
|
|
|
261
305
|
fallback: false
|
|
262
306
|
};
|
|
263
307
|
}
|
|
308
|
+
function createRobotsApiHandler(config) {
|
|
309
|
+
return function handler(_req, res) {
|
|
310
|
+
const robotsTxt = generateRobotsTxt(config);
|
|
311
|
+
res.setHeader("Content-Type", "text/plain");
|
|
312
|
+
res.status(200).send(robotsTxt);
|
|
313
|
+
};
|
|
314
|
+
}
|
|
264
315
|
|
|
316
|
+
exports.createRobotsApiHandler = createRobotsApiHandler;
|
|
265
317
|
exports.createSitemapApiHandler = createSitemapApiHandler;
|
|
266
318
|
exports.createSitemapIndexApiHandler = createSitemapIndexApiHandler;
|
|
319
|
+
exports.generateRobotsTxt = generateRobotsTxt;
|
|
267
320
|
exports.getSitemapStaticPaths = getSitemapStaticPaths;
|
package/dist/pages/index.d.cts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { NextApiRequest, NextApiResponse } from 'next';
|
|
1
|
+
import { MetadataRoute, NextApiRequest, NextApiResponse } from 'next';
|
|
2
|
+
export { MetadataRoute } from 'next';
|
|
2
3
|
|
|
3
4
|
type ChangeFrequency = "always" | "hourly" | "daily" | "weekly" | "monthly" | "yearly" | "never";
|
|
4
5
|
interface SitemapConfig {
|
|
@@ -72,6 +73,12 @@ type PageModule = {
|
|
|
72
73
|
generateStaticParams?: () => Promise<Record<string, string>[]>;
|
|
73
74
|
};
|
|
74
75
|
|
|
76
|
+
/**
|
|
77
|
+
* Generate robots.txt content from configuration
|
|
78
|
+
* Compatible with Next.js MetadataRoute.Robots
|
|
79
|
+
*/
|
|
80
|
+
declare function generateRobotsTxt(config: MetadataRoute.Robots): string;
|
|
81
|
+
|
|
75
82
|
interface CreateSitemapApiHandlerOptions extends SitemapConfig {
|
|
76
83
|
/**
|
|
77
84
|
* The require.context result for page discovery
|
|
@@ -110,5 +117,23 @@ declare function getSitemapStaticPaths(options: CreateSitemapApiHandlerOptions):
|
|
|
110
117
|
}[];
|
|
111
118
|
fallback: boolean;
|
|
112
119
|
}>;
|
|
120
|
+
/**
|
|
121
|
+
* Create API handler for robots.txt
|
|
122
|
+
* Use in: pages/api/robots.txt.ts or pages/robots.txt.ts (with rewrites)
|
|
123
|
+
*
|
|
124
|
+
* @example
|
|
125
|
+
* // pages/api/robots.txt.ts
|
|
126
|
+
* import { createRobotsApiHandler } from "@onruntime/next-sitemap/pages";
|
|
127
|
+
*
|
|
128
|
+
* export default createRobotsApiHandler({
|
|
129
|
+
* rules: {
|
|
130
|
+
* userAgent: "*",
|
|
131
|
+
* allow: "/",
|
|
132
|
+
* disallow: ["/admin", "/private"],
|
|
133
|
+
* },
|
|
134
|
+
* sitemap: "https://example.com/sitemap.xml",
|
|
135
|
+
* });
|
|
136
|
+
*/
|
|
137
|
+
declare function createRobotsApiHandler(config: MetadataRoute.Robots): (_req: NextApiRequest, res: NextApiResponse) => void;
|
|
113
138
|
|
|
114
|
-
export { type ChangeFrequency, type CreateSitemapApiHandlerOptions, type SitemapConfig, type SitemapEntry, createSitemapApiHandler, createSitemapIndexApiHandler, getSitemapStaticPaths };
|
|
139
|
+
export { type ChangeFrequency, type CreateSitemapApiHandlerOptions, type SitemapConfig, type SitemapEntry, createRobotsApiHandler, createSitemapApiHandler, createSitemapIndexApiHandler, generateRobotsTxt, getSitemapStaticPaths };
|
package/dist/pages/index.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { NextApiRequest, NextApiResponse } from 'next';
|
|
1
|
+
import { MetadataRoute, NextApiRequest, NextApiResponse } from 'next';
|
|
2
|
+
export { MetadataRoute } from 'next';
|
|
2
3
|
|
|
3
4
|
type ChangeFrequency = "always" | "hourly" | "daily" | "weekly" | "monthly" | "yearly" | "never";
|
|
4
5
|
interface SitemapConfig {
|
|
@@ -72,6 +73,12 @@ type PageModule = {
|
|
|
72
73
|
generateStaticParams?: () => Promise<Record<string, string>[]>;
|
|
73
74
|
};
|
|
74
75
|
|
|
76
|
+
/**
|
|
77
|
+
* Generate robots.txt content from configuration
|
|
78
|
+
* Compatible with Next.js MetadataRoute.Robots
|
|
79
|
+
*/
|
|
80
|
+
declare function generateRobotsTxt(config: MetadataRoute.Robots): string;
|
|
81
|
+
|
|
75
82
|
interface CreateSitemapApiHandlerOptions extends SitemapConfig {
|
|
76
83
|
/**
|
|
77
84
|
* The require.context result for page discovery
|
|
@@ -110,5 +117,23 @@ declare function getSitemapStaticPaths(options: CreateSitemapApiHandlerOptions):
|
|
|
110
117
|
}[];
|
|
111
118
|
fallback: boolean;
|
|
112
119
|
}>;
|
|
120
|
+
/**
|
|
121
|
+
* Create API handler for robots.txt
|
|
122
|
+
* Use in: pages/api/robots.txt.ts or pages/robots.txt.ts (with rewrites)
|
|
123
|
+
*
|
|
124
|
+
* @example
|
|
125
|
+
* // pages/api/robots.txt.ts
|
|
126
|
+
* import { createRobotsApiHandler } from "@onruntime/next-sitemap/pages";
|
|
127
|
+
*
|
|
128
|
+
* export default createRobotsApiHandler({
|
|
129
|
+
* rules: {
|
|
130
|
+
* userAgent: "*",
|
|
131
|
+
* allow: "/",
|
|
132
|
+
* disallow: ["/admin", "/private"],
|
|
133
|
+
* },
|
|
134
|
+
* sitemap: "https://example.com/sitemap.xml",
|
|
135
|
+
* });
|
|
136
|
+
*/
|
|
137
|
+
declare function createRobotsApiHandler(config: MetadataRoute.Robots): (_req: NextApiRequest, res: NextApiResponse) => void;
|
|
113
138
|
|
|
114
|
-
export { type ChangeFrequency, type CreateSitemapApiHandlerOptions, type SitemapConfig, type SitemapEntry, createSitemapApiHandler, createSitemapIndexApiHandler, getSitemapStaticPaths };
|
|
139
|
+
export { type ChangeFrequency, type CreateSitemapApiHandlerOptions, type SitemapConfig, type SitemapEntry, createRobotsApiHandler, createSitemapApiHandler, createSitemapIndexApiHandler, generateRobotsTxt, getSitemapStaticPaths };
|
package/dist/pages/index.js
CHANGED
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
function calculateDepthPriority(pathname) {
|
|
3
3
|
if (pathname === "/") return 1;
|
|
4
4
|
const depth = pathname.split("/").filter(Boolean).length;
|
|
5
|
-
|
|
5
|
+
const priority = Math.max(0.1, 1 - depth * 0.2);
|
|
6
|
+
return Math.round(priority * 100) / 100;
|
|
6
7
|
}
|
|
7
8
|
function shouldExclude(pathname, exclude) {
|
|
8
9
|
if (!exclude) return false;
|
|
@@ -33,11 +34,16 @@ function getChangeFreq(pathname, changeFreq) {
|
|
|
33
34
|
}
|
|
34
35
|
return changeFreq;
|
|
35
36
|
}
|
|
37
|
+
function normalizePath(pathname) {
|
|
38
|
+
if (pathname === "/") return pathname;
|
|
39
|
+
return pathname.endsWith("/") ? pathname.slice(0, -1) : pathname;
|
|
40
|
+
}
|
|
36
41
|
function buildUrl(baseUrl, pathname, locale, defaultLocale) {
|
|
42
|
+
const normalizedPath = normalizePath(pathname);
|
|
37
43
|
if (!locale || locale === defaultLocale) {
|
|
38
|
-
return `${baseUrl}${
|
|
44
|
+
return `${baseUrl}${normalizedPath}`;
|
|
39
45
|
}
|
|
40
|
-
return `${baseUrl}/${locale}${
|
|
46
|
+
return `${baseUrl}/${locale}${normalizedPath}`;
|
|
41
47
|
}
|
|
42
48
|
function generateSitemapXml(entries) {
|
|
43
49
|
const hasAlternates = entries.some((e) => e.alternates?.languages);
|
|
@@ -92,6 +98,44 @@ ${allEntries}
|
|
|
92
98
|
</sitemapindex>`;
|
|
93
99
|
}
|
|
94
100
|
|
|
101
|
+
// src/pages/robots.ts
|
|
102
|
+
function generateRobotsTxt(config) {
|
|
103
|
+
const lines = [];
|
|
104
|
+
const rules = Array.isArray(config.rules) ? config.rules : [config.rules];
|
|
105
|
+
for (const rule of rules) {
|
|
106
|
+
const userAgents = Array.isArray(rule.userAgent) ? rule.userAgent : [rule.userAgent];
|
|
107
|
+
for (const userAgent of userAgents) {
|
|
108
|
+
lines.push(`User-agent: ${userAgent}`);
|
|
109
|
+
}
|
|
110
|
+
if (rule.allow) {
|
|
111
|
+
const allows = Array.isArray(rule.allow) ? rule.allow : [rule.allow];
|
|
112
|
+
for (const allow of allows) {
|
|
113
|
+
lines.push(`Allow: ${allow}`);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
if (rule.disallow) {
|
|
117
|
+
const disallows = Array.isArray(rule.disallow) ? rule.disallow : [rule.disallow];
|
|
118
|
+
for (const disallow of disallows) {
|
|
119
|
+
lines.push(`Disallow: ${disallow}`);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
if (rule.crawlDelay !== void 0) {
|
|
123
|
+
lines.push(`Crawl-delay: ${rule.crawlDelay}`);
|
|
124
|
+
}
|
|
125
|
+
lines.push("");
|
|
126
|
+
}
|
|
127
|
+
if (config.sitemap) {
|
|
128
|
+
const sitemaps = Array.isArray(config.sitemap) ? config.sitemap : [config.sitemap];
|
|
129
|
+
for (const sitemap of sitemaps) {
|
|
130
|
+
lines.push(`Sitemap: ${sitemap}`);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
if (config.host) {
|
|
134
|
+
lines.push(`Host: ${config.host}`);
|
|
135
|
+
}
|
|
136
|
+
return lines.join("\n").trim() + "\n";
|
|
137
|
+
}
|
|
138
|
+
|
|
95
139
|
// src/pages/index.ts
|
|
96
140
|
function extractRoutes(pagesContext, localeSegment) {
|
|
97
141
|
const routes = [];
|
|
@@ -259,5 +303,12 @@ async function getSitemapStaticPaths(options) {
|
|
|
259
303
|
fallback: false
|
|
260
304
|
};
|
|
261
305
|
}
|
|
306
|
+
function createRobotsApiHandler(config) {
|
|
307
|
+
return function handler(_req, res) {
|
|
308
|
+
const robotsTxt = generateRobotsTxt(config);
|
|
309
|
+
res.setHeader("Content-Type", "text/plain");
|
|
310
|
+
res.status(200).send(robotsTxt);
|
|
311
|
+
};
|
|
312
|
+
}
|
|
262
313
|
|
|
263
|
-
export { createSitemapApiHandler, createSitemapIndexApiHandler, getSitemapStaticPaths };
|
|
314
|
+
export { createRobotsApiHandler, createSitemapApiHandler, createSitemapIndexApiHandler, generateRobotsTxt, getSitemapStaticPaths };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@onruntime/next-sitemap",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.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": {
|
|
@@ -45,6 +45,7 @@
|
|
|
45
45
|
"@types/node": "^22.0.0",
|
|
46
46
|
"next": "^16.1.1",
|
|
47
47
|
"tsup": "^8",
|
|
48
|
+
"tsx": "^4",
|
|
48
49
|
"typescript": "^5"
|
|
49
50
|
},
|
|
50
51
|
"keywords": [
|
|
@@ -60,6 +61,7 @@
|
|
|
60
61
|
"scripts": {
|
|
61
62
|
"build": "tsup",
|
|
62
63
|
"dev": "tsup --watch",
|
|
63
|
-
"type-check": "tsc --noEmit"
|
|
64
|
+
"type-check": "tsc --noEmit",
|
|
65
|
+
"test": "tsx --test tests/index.ts"
|
|
64
66
|
}
|
|
65
67
|
}
|