@agent-seo/next 1.0.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/LICENSE +21 -0
- package/dist/index.cjs +333 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +59 -0
- package/dist/index.d.ts +59 -0
- package/dist/index.js +312 -0
- package/dist/index.js.map +1 -0
- package/dist/middleware.cjs +86 -0
- package/dist/middleware.cjs.map +1 -0
- package/dist/middleware.d.cts +22 -0
- package/dist/middleware.d.ts +22 -0
- package/dist/middleware.js +61 -0
- package/dist/middleware.js.map +1 -0
- package/package.json +72 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Pontiggia
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,333 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
createAgentSeoMiddleware: () => createAgentSeoMiddleware,
|
|
24
|
+
createLlmsTxtHandler: () => createLlmsTxtHandler,
|
|
25
|
+
discoverNextRoutes: () => import_core2.discoverNextRoutes,
|
|
26
|
+
generateLlmsTxt: () => import_core2.generateLlmsTxt,
|
|
27
|
+
transform: () => import_core2.transform,
|
|
28
|
+
withAgentSeo: () => withAgentSeo
|
|
29
|
+
});
|
|
30
|
+
module.exports = __toCommonJS(index_exports);
|
|
31
|
+
|
|
32
|
+
// src/plugin.ts
|
|
33
|
+
var import_node_fs = require("fs");
|
|
34
|
+
var import_node_path = require("path");
|
|
35
|
+
function withAgentSeo(agentSeoOptions) {
|
|
36
|
+
const appDir = agentSeoOptions.appDir || detectAppDir();
|
|
37
|
+
if (appDir) {
|
|
38
|
+
generateLlmsTxtRoute(appDir, agentSeoOptions);
|
|
39
|
+
generateTransformRoute(appDir, agentSeoOptions);
|
|
40
|
+
generateRobotsTxt(appDir, agentSeoOptions);
|
|
41
|
+
}
|
|
42
|
+
return (nextConfig = {}) => {
|
|
43
|
+
return {
|
|
44
|
+
...nextConfig,
|
|
45
|
+
async headers() {
|
|
46
|
+
const existingHeaders = await (nextConfig.headers?.() ?? []);
|
|
47
|
+
return [
|
|
48
|
+
...existingHeaders,
|
|
49
|
+
{
|
|
50
|
+
source: "/((?!api|_next|static|favicon.ico).*)",
|
|
51
|
+
headers: [{ key: "Vary", value: "Accept, User-Agent" }]
|
|
52
|
+
}
|
|
53
|
+
];
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
function detectAppDir() {
|
|
59
|
+
const cwd = process.cwd();
|
|
60
|
+
const candidates = [(0, import_node_path.join)(cwd, "app"), (0, import_node_path.join)(cwd, "src", "app")];
|
|
61
|
+
for (const candidate of candidates) {
|
|
62
|
+
if ((0, import_node_fs.existsSync)(candidate)) {
|
|
63
|
+
return candidate;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
var GENERATED_BANNER = `// AUTO-GENERATED by @agent-seo/next \u2014 do not edit manually.
|
|
69
|
+
// This file is created by withAgentSeo() in next.config.ts.
|
|
70
|
+
// Add "app/llms.txt" to your .gitignore.
|
|
71
|
+
`;
|
|
72
|
+
function generateLlmsTxtRoute(appDir, options) {
|
|
73
|
+
const routeDir = (0, import_node_path.join)(appDir, "llms.txt");
|
|
74
|
+
const routeFile = (0, import_node_path.join)(routeDir, "route.js");
|
|
75
|
+
const siteName = escapeStr(options.siteName);
|
|
76
|
+
const siteDescription = escapeStr(options.siteDescription);
|
|
77
|
+
const baseUrl = escapeStr(options.baseUrl);
|
|
78
|
+
const excludePatterns = JSON.stringify(options.exclude || ["/api"]);
|
|
79
|
+
const content = `${GENERATED_BANNER}
|
|
80
|
+
import { generateLlmsTxt, discoverNextRoutes } from '@agent-seo/next';
|
|
81
|
+
import path from 'node:path';
|
|
82
|
+
|
|
83
|
+
const appDir = path.resolve(process.cwd(), 'app');
|
|
84
|
+
|
|
85
|
+
export async function GET(request) {
|
|
86
|
+
const url = new URL(request.url);
|
|
87
|
+
|
|
88
|
+
const routes = discoverNextRoutes(appDir, {
|
|
89
|
+
exclude: ${excludePatterns},
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
const result = generateLlmsTxt(
|
|
93
|
+
{
|
|
94
|
+
siteName: '${siteName}',
|
|
95
|
+
siteDescription: '${siteDescription}',
|
|
96
|
+
baseUrl: process.env.NEXT_PUBLIC_BASE_URL || '${baseUrl}',
|
|
97
|
+
},
|
|
98
|
+
routes,
|
|
99
|
+
);
|
|
100
|
+
|
|
101
|
+
return new Response(result.llmsTxt, {
|
|
102
|
+
status: 200,
|
|
103
|
+
headers: {
|
|
104
|
+
'Content-Type': 'text/plain; charset=utf-8',
|
|
105
|
+
'Content-Disposition': 'inline',
|
|
106
|
+
'Cache-Control': 'public, max-age=3600, s-maxage=3600',
|
|
107
|
+
},
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
`;
|
|
111
|
+
(0, import_node_fs.mkdirSync)(routeDir, { recursive: true });
|
|
112
|
+
let existingContent = "";
|
|
113
|
+
try {
|
|
114
|
+
const { readFileSync } = require("fs");
|
|
115
|
+
existingContent = readFileSync(routeFile, "utf-8");
|
|
116
|
+
} catch {
|
|
117
|
+
}
|
|
118
|
+
if (existingContent !== content) {
|
|
119
|
+
(0, import_node_fs.writeFileSync)(routeFile, content, "utf-8");
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
function escapeStr(s) {
|
|
123
|
+
return s.replace(/\\/g, "\\\\").replace(/'/g, "\\'");
|
|
124
|
+
}
|
|
125
|
+
function generateTransformRoute(appDir, options) {
|
|
126
|
+
const routeDir = (0, import_node_path.join)(appDir, "api", "agent-seo-transform");
|
|
127
|
+
const routeFile = (0, import_node_path.join)(routeDir, "route.js");
|
|
128
|
+
const baseUrl = escapeStr(options.baseUrl);
|
|
129
|
+
const content = `${GENERATED_BANNER}
|
|
130
|
+
import { transform } from '@agent-seo/next';
|
|
131
|
+
|
|
132
|
+
// Force Node.js runtime (required for jsdom / Readability / Turndown)
|
|
133
|
+
export const runtime = 'nodejs';
|
|
134
|
+
export const dynamic = 'force-dynamic';
|
|
135
|
+
|
|
136
|
+
export async function GET(request) {
|
|
137
|
+
const { searchParams } = new URL(request.url);
|
|
138
|
+
const pagePath = searchParams.get('path') || '/';
|
|
139
|
+
|
|
140
|
+
// Build the internal URL to fetch the original HTML page
|
|
141
|
+
const origin = process.env.NEXT_PUBLIC_BASE_URL || '${baseUrl}';
|
|
142
|
+
if (!origin) {
|
|
143
|
+
return new Response('Base URL not configured', { status: 500 });
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Only allow same-origin, absolute paths (prevent SSRF via absolute or protocol-relative URLs)
|
|
147
|
+
const isValidPath =
|
|
148
|
+
pagePath.startsWith('/') &&
|
|
149
|
+
!pagePath.startsWith('//') &&
|
|
150
|
+
!pagePath.includes('://') &&
|
|
151
|
+
!pagePath.includes('\\\\');
|
|
152
|
+
if (!isValidPath) {
|
|
153
|
+
return new Response('Invalid path', { status: 400 });
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const originUrl = new URL(origin);
|
|
157
|
+
if (originUrl.protocol !== 'http:' && originUrl.protocol !== 'https:') {
|
|
158
|
+
return new Response('Invalid base URL', { status: 500 });
|
|
159
|
+
}
|
|
160
|
+
const pageUrl = new URL(pagePath, originUrl.origin);
|
|
161
|
+
if (pageUrl.origin !== originUrl.origin) {
|
|
162
|
+
return new Response('Invalid origin', { status: 400 });
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
try {
|
|
166
|
+
// Fetch the page HTML from the local server with a normal User-Agent
|
|
167
|
+
// to avoid infinite rewrite loops
|
|
168
|
+
const res = await fetch(pageUrl.toString(), {
|
|
169
|
+
headers: {
|
|
170
|
+
'User-Agent': 'AgentSEO-Internal/1.0',
|
|
171
|
+
'Accept': 'text/html',
|
|
172
|
+
},
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
if (!res.ok) {
|
|
176
|
+
return new Response('Page not found', { status: 404 });
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
const html = await res.text();
|
|
180
|
+
|
|
181
|
+
const result = await transform(html, {
|
|
182
|
+
url: pageUrl.toString(),
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
return new Response(result.markdown, {
|
|
186
|
+
status: 200,
|
|
187
|
+
headers: {
|
|
188
|
+
'Content-Type': 'text/markdown; charset=utf-8',
|
|
189
|
+
'Content-Disposition': 'inline',
|
|
190
|
+
'Cache-Control': 'public, max-age=3600, s-maxage=3600',
|
|
191
|
+
'Vary': 'Accept, User-Agent',
|
|
192
|
+
'X-Robots-Tag': 'all',
|
|
193
|
+
'X-Agent-Seo': 'transformed',
|
|
194
|
+
},
|
|
195
|
+
});
|
|
196
|
+
} catch (err) {
|
|
197
|
+
console.error('[agent-seo] Transform error:', err);
|
|
198
|
+
return new Response('Transform failed', { status: 500 });
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
`;
|
|
202
|
+
(0, import_node_fs.mkdirSync)(routeDir, { recursive: true });
|
|
203
|
+
let existingContent = "";
|
|
204
|
+
try {
|
|
205
|
+
const { readFileSync } = require("fs");
|
|
206
|
+
existingContent = readFileSync(routeFile, "utf-8");
|
|
207
|
+
} catch {
|
|
208
|
+
}
|
|
209
|
+
if (existingContent !== content) {
|
|
210
|
+
(0, import_node_fs.writeFileSync)(routeFile, content, "utf-8");
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
function generateRobotsTxt(appDir, options) {
|
|
214
|
+
const robotsFile = (0, import_node_path.join)(appDir, "robots.txt");
|
|
215
|
+
if ((0, import_node_fs.existsSync)(robotsFile)) {
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
218
|
+
let content = `# AI-Optimized robots.txt \u2014 Generated by @agent-seo/next
|
|
219
|
+
# For AI agents: see /llms.txt for a structured site manifest
|
|
220
|
+
|
|
221
|
+
User-agent: *
|
|
222
|
+
Allow: /
|
|
223
|
+
`;
|
|
224
|
+
if (options.sitemap) {
|
|
225
|
+
const sitemapUrl = typeof options.sitemap === "string" ? options.sitemap : `${options.baseUrl}/sitemap.xml`;
|
|
226
|
+
content += `
|
|
227
|
+
Sitemap: ${sitemapUrl}
|
|
228
|
+
`;
|
|
229
|
+
}
|
|
230
|
+
(0, import_node_fs.writeFileSync)(robotsFile, content, "utf-8");
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// src/middleware.ts
|
|
234
|
+
var import_edge = require("@agent-seo/core/edge");
|
|
235
|
+
var import_server = require("next/server");
|
|
236
|
+
var ALWAYS_SKIP = /* @__PURE__ */ new Set([
|
|
237
|
+
"/favicon.ico",
|
|
238
|
+
"/robots.txt",
|
|
239
|
+
"/sitemap.xml",
|
|
240
|
+
"/llms.txt",
|
|
241
|
+
"/llms-full.txt"
|
|
242
|
+
]);
|
|
243
|
+
var DEFAULT_EXCLUDE = [
|
|
244
|
+
"/api/**",
|
|
245
|
+
"/_next/**"
|
|
246
|
+
];
|
|
247
|
+
function createAgentSeoMiddleware(options = {}) {
|
|
248
|
+
const excludePatterns = [...DEFAULT_EXCLUDE, ...options.exclude || []];
|
|
249
|
+
return function middleware(request) {
|
|
250
|
+
const { pathname } = request.nextUrl;
|
|
251
|
+
if (ALWAYS_SKIP.has(pathname)) {
|
|
252
|
+
return import_server.NextResponse.next();
|
|
253
|
+
}
|
|
254
|
+
if (isExcluded(pathname, excludePatterns)) {
|
|
255
|
+
return import_server.NextResponse.next();
|
|
256
|
+
}
|
|
257
|
+
const ua = request.headers.get("user-agent");
|
|
258
|
+
const accept = request.headers.get("accept");
|
|
259
|
+
const aiCtx = (0, import_edge.detectAgent)(ua, accept);
|
|
260
|
+
if (pathname.endsWith(".md")) {
|
|
261
|
+
const originalPath = pathname.slice(0, -3) || "/";
|
|
262
|
+
const transformUrl = new URL("/api/agent-seo-transform", request.url);
|
|
263
|
+
transformUrl.searchParams.set("path", originalPath);
|
|
264
|
+
return setBotHeaders(import_server.NextResponse.rewrite(transformUrl));
|
|
265
|
+
}
|
|
266
|
+
if (aiCtx.isAIBot) {
|
|
267
|
+
const transformUrl = new URL("/api/agent-seo-transform", request.url);
|
|
268
|
+
transformUrl.searchParams.set("path", pathname);
|
|
269
|
+
return setBotHeaders(import_server.NextResponse.rewrite(transformUrl));
|
|
270
|
+
}
|
|
271
|
+
const response = import_server.NextResponse.next();
|
|
272
|
+
response.headers.set("Vary", "Accept, User-Agent");
|
|
273
|
+
return response;
|
|
274
|
+
};
|
|
275
|
+
}
|
|
276
|
+
function setBotHeaders(response) {
|
|
277
|
+
response.headers.set("Content-Disposition", "inline");
|
|
278
|
+
response.headers.set("Vary", "Accept, User-Agent");
|
|
279
|
+
response.headers.set("X-Robots-Tag", "all");
|
|
280
|
+
response.headers.delete("x-nextjs-matched-path");
|
|
281
|
+
return response;
|
|
282
|
+
}
|
|
283
|
+
function isExcluded(path, patterns) {
|
|
284
|
+
return patterns.some((pattern) => matchGlob(pattern, path));
|
|
285
|
+
}
|
|
286
|
+
function matchGlob(pattern, path) {
|
|
287
|
+
const regex = pattern.replace(/\*\*/g, "{{DOUBLESTAR}}").replace(/\*/g, "[^/]*").replace(/{{DOUBLESTAR}}/g, ".*");
|
|
288
|
+
return new RegExp(`^${regex}$`).test(path);
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
// src/route-handler.ts
|
|
292
|
+
var import_core = require("@agent-seo/core");
|
|
293
|
+
function createLlmsTxtHandler(options) {
|
|
294
|
+
return async function GET() {
|
|
295
|
+
let routes = options.llmsTxt?.routes || [];
|
|
296
|
+
if (routes.length === 0 && options.appDir) {
|
|
297
|
+
routes = (0, import_core.discoverNextRoutes)(options.appDir, {
|
|
298
|
+
exclude: options.exclude || ["/api"],
|
|
299
|
+
...options.discoverOptions
|
|
300
|
+
});
|
|
301
|
+
}
|
|
302
|
+
const result = (0, import_core.generateLlmsTxt)(
|
|
303
|
+
{
|
|
304
|
+
siteName: options.siteName,
|
|
305
|
+
siteDescription: options.siteDescription,
|
|
306
|
+
baseUrl: options.baseUrl,
|
|
307
|
+
...options.llmsTxt
|
|
308
|
+
},
|
|
309
|
+
routes
|
|
310
|
+
);
|
|
311
|
+
const content = options.full ? result.llmsFullTxt : result.llmsTxt;
|
|
312
|
+
return new Response(content, {
|
|
313
|
+
status: 200,
|
|
314
|
+
headers: {
|
|
315
|
+
"Content-Type": "text/plain; charset=utf-8",
|
|
316
|
+
"Cache-Control": "public, max-age=3600, s-maxage=3600"
|
|
317
|
+
}
|
|
318
|
+
});
|
|
319
|
+
};
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
// src/index.ts
|
|
323
|
+
var import_core2 = require("@agent-seo/core");
|
|
324
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
325
|
+
0 && (module.exports = {
|
|
326
|
+
createAgentSeoMiddleware,
|
|
327
|
+
createLlmsTxtHandler,
|
|
328
|
+
discoverNextRoutes,
|
|
329
|
+
generateLlmsTxt,
|
|
330
|
+
transform,
|
|
331
|
+
withAgentSeo
|
|
332
|
+
});
|
|
333
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/plugin.ts","../src/middleware.ts","../src/route-handler.ts"],"sourcesContent":["export { withAgentSeo } from './plugin.js';\nexport type { WithAgentSeoOptions } from './plugin.js';\nexport { createAgentSeoMiddleware } from './middleware.js';\nexport type { AgentSeoMiddlewareOptions } from './middleware.js';\nexport { createLlmsTxtHandler } from './route-handler.js';\nexport type { LlmsTxtHandlerOptions } from './route-handler.js';\nexport {\n generateLlmsTxt,\n discoverNextRoutes,\n transform,\n} from '@agent-seo/core';\nexport type {\n AgentSeoOptions,\n AIRequestContext,\n TransformResult,\n BotInfo,\n BotPurpose,\n LlmsTxtRoute,\n DiscoverOptions,\n} from '@agent-seo/core';\n","import { existsSync, mkdirSync, writeFileSync } from 'node:fs';\nimport { join, resolve } from 'node:path';\nimport type { NextConfig } from 'next';\nimport type { AgentSeoOptions } from '@agent-seo/core';\n\nexport interface WithAgentSeoOptions extends AgentSeoOptions {\n /**\n * Absolute path to the Next.js `app/` directory.\n * If omitted, auto-detected by checking `./app` and `./src/app` from cwd.\n */\n appDir?: string;\n /**\n * Sitemap URL for the `robots.txt` Sitemap directive.\n * - `true`: uses `{baseUrl}/sitemap.xml`\n * - `string`: uses the provided URL\n * - `undefined`/`false`: no Sitemap directive\n */\n sitemap?: boolean | string;\n}\n\n/**\n * Next.js config plugin that:\n * 1. Auto-generates `app/llms.txt/route.ts` (zero-config `/llms.txt` endpoint)\n * 2. Injects `Vary: Accept, User-Agent` headers on all pages\n *\n * @example\n * ```ts\n * // next.config.ts\n * import { withAgentSeo } from '@agent-seo/next';\n *\n * export default withAgentSeo({\n * siteName: 'My App',\n * siteDescription: 'A brief description for LLMs.',\n * baseUrl: 'https://myapp.com',\n * })({});\n * ```\n */\nexport function withAgentSeo(agentSeoOptions: WithAgentSeoOptions) {\n const appDir = agentSeoOptions.appDir || detectAppDir();\n\n // Auto-generate route handlers at config-evaluation time\n if (appDir) {\n generateLlmsTxtRoute(appDir, agentSeoOptions);\n generateTransformRoute(appDir, agentSeoOptions);\n generateRobotsTxt(appDir, agentSeoOptions);\n }\n\n return (nextConfig: NextConfig = {}): NextConfig => {\n return {\n ...nextConfig,\n\n async headers() {\n const existingHeaders = await (nextConfig.headers?.() ?? []);\n return [\n ...existingHeaders,\n {\n source: '/((?!api|_next|static|favicon.ico).*)',\n headers: [{ key: 'Vary', value: 'Accept, User-Agent' }],\n },\n ];\n },\n };\n };\n}\n\n/**\n * Auto-detect the Next.js `app/` directory from common locations.\n */\nfunction detectAppDir(): string | null {\n const cwd = process.cwd();\n const candidates = [join(cwd, 'app'), join(cwd, 'src', 'app')];\n\n for (const candidate of candidates) {\n if (existsSync(candidate)) {\n return candidate;\n }\n }\n\n return null;\n}\n\n// Auto-generated file banner\nconst GENERATED_BANNER = `// AUTO-GENERATED by @agent-seo/next — do not edit manually.\n// This file is created by withAgentSeo() in next.config.ts.\n// Add \"app/llms.txt\" to your .gitignore.\n`;\n\n/**\n * Write `app/llms.txt/route.js` with a handler that auto-discovers routes.\n */\nfunction generateLlmsTxtRoute(\n appDir: string,\n options: WithAgentSeoOptions,\n): void {\n const routeDir = join(appDir, 'llms.txt');\n const routeFile = join(routeDir, 'route.js');\n\n // Serialize the options we need into the generated file\n const siteName = escapeStr(options.siteName);\n const siteDescription = escapeStr(options.siteDescription);\n const baseUrl = escapeStr(options.baseUrl);\n const excludePatterns = JSON.stringify(options.exclude || ['/api']);\n\n const content = `${GENERATED_BANNER}\nimport { generateLlmsTxt, discoverNextRoutes } from '@agent-seo/next';\nimport path from 'node:path';\n\nconst appDir = path.resolve(process.cwd(), 'app');\n\nexport async function GET(request) {\n const url = new URL(request.url);\n\n const routes = discoverNextRoutes(appDir, {\n exclude: ${excludePatterns},\n });\n\n const result = generateLlmsTxt(\n {\n siteName: '${siteName}',\n siteDescription: '${siteDescription}',\n baseUrl: process.env.NEXT_PUBLIC_BASE_URL || '${baseUrl}',\n },\n routes,\n );\n\n return new Response(result.llmsTxt, {\n status: 200,\n headers: {\n 'Content-Type': 'text/plain; charset=utf-8',\n 'Content-Disposition': 'inline',\n 'Cache-Control': 'public, max-age=3600, s-maxage=3600',\n },\n });\n}\n`;\n\n // Only write if the file doesn't exist or content has changed\n mkdirSync(routeDir, { recursive: true });\n\n let existingContent = '';\n try {\n const { readFileSync } = require('node:fs');\n existingContent = readFileSync(routeFile, 'utf-8');\n } catch {\n // File doesn't exist yet\n }\n\n if (existingContent !== content) {\n writeFileSync(routeFile, content, 'utf-8');\n }\n}\n\nfunction escapeStr(s: string): string {\n return s.replace(/\\\\/g, '\\\\\\\\').replace(/'/g, \"\\\\'\");\n}\n\n/**\n * Write `app/api/agent-seo-transform/route.js` — a Node.js API route that\n * receives a `?path=/some-page` param, fetches the HTML from the local\n * Next.js server, runs the core `transform()` pipeline (JSDOM → Readability\n * → Turndown), and returns clean Markdown.\n */\nfunction generateTransformRoute(\n appDir: string,\n options: WithAgentSeoOptions,\n): void {\n const routeDir = join(appDir, 'api', 'agent-seo-transform');\n const routeFile = join(routeDir, 'route.js');\n\n const baseUrl = escapeStr(options.baseUrl);\n\n const content = `${GENERATED_BANNER}\nimport { transform } from '@agent-seo/next';\n\n// Force Node.js runtime (required for jsdom / Readability / Turndown)\nexport const runtime = 'nodejs';\nexport const dynamic = 'force-dynamic';\n\nexport async function GET(request) {\n const { searchParams } = new URL(request.url);\n const pagePath = searchParams.get('path') || '/';\n\n // Build the internal URL to fetch the original HTML page\n const origin = process.env.NEXT_PUBLIC_BASE_URL || '${baseUrl}';\n if (!origin) {\n return new Response('Base URL not configured', { status: 500 });\n }\n\n // Only allow same-origin, absolute paths (prevent SSRF via absolute or protocol-relative URLs)\n const isValidPath =\n pagePath.startsWith('/') &&\n !pagePath.startsWith('//') &&\n !pagePath.includes('://') &&\n !pagePath.includes('\\\\\\\\');\n if (!isValidPath) {\n return new Response('Invalid path', { status: 400 });\n }\n\n const originUrl = new URL(origin);\n if (originUrl.protocol !== 'http:' && originUrl.protocol !== 'https:') {\n return new Response('Invalid base URL', { status: 500 });\n }\n const pageUrl = new URL(pagePath, originUrl.origin);\n if (pageUrl.origin !== originUrl.origin) {\n return new Response('Invalid origin', { status: 400 });\n }\n\n try {\n // Fetch the page HTML from the local server with a normal User-Agent\n // to avoid infinite rewrite loops\n const res = await fetch(pageUrl.toString(), {\n headers: {\n 'User-Agent': 'AgentSEO-Internal/1.0',\n 'Accept': 'text/html',\n },\n });\n\n if (!res.ok) {\n return new Response('Page not found', { status: 404 });\n }\n\n const html = await res.text();\n\n const result = await transform(html, {\n url: pageUrl.toString(),\n });\n\n return new Response(result.markdown, {\n status: 200,\n headers: {\n 'Content-Type': 'text/markdown; charset=utf-8',\n 'Content-Disposition': 'inline',\n 'Cache-Control': 'public, max-age=3600, s-maxage=3600',\n 'Vary': 'Accept, User-Agent',\n 'X-Robots-Tag': 'all',\n 'X-Agent-Seo': 'transformed',\n },\n });\n } catch (err) {\n console.error('[agent-seo] Transform error:', err);\n return new Response('Transform failed', { status: 500 });\n }\n}\n`;\n\n mkdirSync(routeDir, { recursive: true });\n\n let existingContent = '';\n try {\n const { readFileSync } = require('node:fs');\n existingContent = readFileSync(routeFile, 'utf-8');\n } catch {\n // File doesn't exist yet\n }\n\n if (existingContent !== content) {\n writeFileSync(routeFile, content, 'utf-8');\n }\n}\n\n/**\n * Write `app/robots.txt` — a static robots.txt that allows all crawlers\n * and references /llms.txt for AI agents.\n * Only created if the file doesn't already exist (won't overwrite user customizations).\n */\nfunction generateRobotsTxt(\n appDir: string,\n options: WithAgentSeoOptions,\n): void {\n const robotsFile = join(appDir, 'robots.txt');\n\n // Don't overwrite user-created robots.txt\n if (existsSync(robotsFile)) {\n return;\n }\n\n let content = `# AI-Optimized robots.txt — Generated by @agent-seo/next\n# For AI agents: see /llms.txt for a structured site manifest\n\nUser-agent: *\nAllow: /\n`;\n\n if (options.sitemap) {\n const sitemapUrl =\n typeof options.sitemap === 'string'\n ? options.sitemap\n : `${options.baseUrl}/sitemap.xml`;\n content += `\\nSitemap: ${sitemapUrl}\\n`;\n }\n\n writeFileSync(robotsFile, content, 'utf-8');\n}\n","import { detectAgent } from '@agent-seo/core/edge';\nimport { NextResponse } from 'next/server';\nimport type { NextRequest } from 'next/server';\n\nexport interface AgentSeoMiddlewareOptions {\n /**\n * Glob patterns for paths that should NEVER be rewritten to Markdown.\n * Merged with built-in defaults (see `DEFAULT_EXCLUDE`).\n *\n * @example ['\\/dashboard\\/**', '\\/admin\\/**', '\\/api\\/private\\/**']\n */\n exclude?: string[];\n}\n\n/** Paths that are always skipped — framework internals + standard files. */\nconst ALWAYS_SKIP = new Set([\n '/favicon.ico',\n '/robots.txt',\n '/sitemap.xml',\n '/llms.txt',\n '/llms-full.txt',\n]);\n\n/** Default exclude patterns — users can extend but not remove these. */\nconst DEFAULT_EXCLUDE: string[] = [\n '/api/**',\n '/_next/**',\n];\n\n/**\n * Creates a Next.js middleware that:\n * 1. Detects AI bot requests via User-Agent\n * 2. Rewrites AI bot requests to an internal transform API route\n * that converts HTML → Markdown (runs on Node.js runtime)\n * 3. Handles `.md` suffix requests (e.g. `/about.md` → transform `/about`)\n * 4. Sets `Vary: Accept, User-Agent` on all responses\n */\nexport function createAgentSeoMiddleware(options: AgentSeoMiddlewareOptions = {}) {\n const excludePatterns = [...DEFAULT_EXCLUDE, ...(options.exclude || [])];\n\n return function middleware(request: NextRequest) {\n const { pathname } = request.nextUrl;\n\n // Skip standard files (robots.txt, favicon, llms.txt, etc.)\n if (ALWAYS_SKIP.has(pathname)) {\n return NextResponse.next();\n }\n\n // Skip excluded patterns (API routes, admin, dashboard, etc.)\n if (isExcluded(pathname, excludePatterns)) {\n return NextResponse.next();\n }\n\n const ua = request.headers.get('user-agent');\n const accept = request.headers.get('accept');\n const aiCtx = detectAgent(ua, accept);\n\n // Handle explicit .md suffix requests (e.g. /about.md)\n if (pathname.endsWith('.md')) {\n const originalPath = pathname.slice(0, -3) || '/';\n const transformUrl = new URL('/api/agent-seo-transform', request.url);\n transformUrl.searchParams.set('path', originalPath);\n return setBotHeaders(NextResponse.rewrite(transformUrl));\n }\n\n // If AI bot, rewrite to transform API\n if (aiCtx.isAIBot) {\n const transformUrl = new URL('/api/agent-seo-transform', request.url);\n transformUrl.searchParams.set('path', pathname);\n return setBotHeaders(NextResponse.rewrite(transformUrl));\n }\n\n // Normal request — just add Vary header\n const response = NextResponse.next();\n response.headers.set('Vary', 'Accept, User-Agent');\n return response;\n };\n}\n\n/**\n * Set clean, bot-friendly headers on a rewrite response.\n * Overrides Next.js RSC-related headers that can confuse AI bots.\n */\nfunction setBotHeaders(response: NextResponse): NextResponse {\n response.headers.set('Content-Disposition', 'inline');\n response.headers.set('Vary', 'Accept, User-Agent');\n response.headers.set('X-Robots-Tag', 'all');\n response.headers.delete('x-nextjs-matched-path');\n return response;\n}\n\nfunction isExcluded(path: string, patterns: string[]): boolean {\n return patterns.some((pattern) => matchGlob(pattern, path));\n}\n\nfunction matchGlob(pattern: string, path: string): boolean {\n const regex = pattern\n .replace(/\\*\\*/g, '{{DOUBLESTAR}}')\n .replace(/\\*/g, '[^/]*')\n .replace(/{{DOUBLESTAR}}/g, '.*');\n return new RegExp(`^${regex}$`).test(path);\n}\n","import { generateLlmsTxt, discoverNextRoutes } from '@agent-seo/core';\nimport type { AgentSeoOptions, DiscoverOptions } from '@agent-seo/core';\n\nexport interface LlmsTxtHandlerOptions extends AgentSeoOptions {\n /** Return llms-full.txt instead of llms.txt */\n full?: boolean;\n\n /**\n * Absolute path to the Next.js `app/` directory for automatic route discovery.\n * When set, routes for llms.txt are auto-discovered by scanning page.tsx files.\n * Titles and descriptions are extracted from `export const metadata` in each page.\n *\n * @example\n * ```ts\n * appDir: path.resolve(process.cwd(), 'app')\n * ```\n */\n appDir?: string;\n\n /** Options for route discovery when using appDir */\n discoverOptions?: DiscoverOptions;\n}\n\nexport function createLlmsTxtHandler(options: LlmsTxtHandlerOptions) {\n return async function GET() {\n // Auto-discover routes from app/ directory if no explicit routes are provided\n let routes = options.llmsTxt?.routes || [];\n\n if (routes.length === 0 && options.appDir) {\n routes = discoverNextRoutes(options.appDir, {\n exclude: options.exclude || ['/api'],\n ...options.discoverOptions,\n });\n }\n\n const result = generateLlmsTxt(\n {\n siteName: options.siteName,\n siteDescription: options.siteDescription,\n baseUrl: options.baseUrl,\n ...options.llmsTxt,\n },\n routes,\n );\n\n const content = options.full ? result.llmsFullTxt : result.llmsTxt;\n\n return new Response(content, {\n status: 200,\n headers: {\n 'Content-Type': 'text/plain; charset=utf-8',\n 'Cache-Control': 'public, max-age=3600, s-maxage=3600',\n },\n });\n };\n}\n"],"mappings":"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","names":["import_core"]}
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { NextConfig } from 'next';
|
|
2
|
+
import { AgentSeoOptions, DiscoverOptions } from '@agent-seo/core';
|
|
3
|
+
export { AIRequestContext, AgentSeoOptions, BotInfo, BotPurpose, DiscoverOptions, LlmsTxtRoute, TransformResult, discoverNextRoutes, generateLlmsTxt, transform } from '@agent-seo/core';
|
|
4
|
+
export { AgentSeoMiddlewareOptions, createAgentSeoMiddleware } from './middleware.cjs';
|
|
5
|
+
import 'next/server';
|
|
6
|
+
|
|
7
|
+
interface WithAgentSeoOptions extends AgentSeoOptions {
|
|
8
|
+
/**
|
|
9
|
+
* Absolute path to the Next.js `app/` directory.
|
|
10
|
+
* If omitted, auto-detected by checking `./app` and `./src/app` from cwd.
|
|
11
|
+
*/
|
|
12
|
+
appDir?: string;
|
|
13
|
+
/**
|
|
14
|
+
* Sitemap URL for the `robots.txt` Sitemap directive.
|
|
15
|
+
* - `true`: uses `{baseUrl}/sitemap.xml`
|
|
16
|
+
* - `string`: uses the provided URL
|
|
17
|
+
* - `undefined`/`false`: no Sitemap directive
|
|
18
|
+
*/
|
|
19
|
+
sitemap?: boolean | string;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Next.js config plugin that:
|
|
23
|
+
* 1. Auto-generates `app/llms.txt/route.ts` (zero-config `/llms.txt` endpoint)
|
|
24
|
+
* 2. Injects `Vary: Accept, User-Agent` headers on all pages
|
|
25
|
+
*
|
|
26
|
+
* @example
|
|
27
|
+
* ```ts
|
|
28
|
+
* // next.config.ts
|
|
29
|
+
* import { withAgentSeo } from '@agent-seo/next';
|
|
30
|
+
*
|
|
31
|
+
* export default withAgentSeo({
|
|
32
|
+
* siteName: 'My App',
|
|
33
|
+
* siteDescription: 'A brief description for LLMs.',
|
|
34
|
+
* baseUrl: 'https://myapp.com',
|
|
35
|
+
* })({});
|
|
36
|
+
* ```
|
|
37
|
+
*/
|
|
38
|
+
declare function withAgentSeo(agentSeoOptions: WithAgentSeoOptions): (nextConfig?: NextConfig) => NextConfig;
|
|
39
|
+
|
|
40
|
+
interface LlmsTxtHandlerOptions extends AgentSeoOptions {
|
|
41
|
+
/** Return llms-full.txt instead of llms.txt */
|
|
42
|
+
full?: boolean;
|
|
43
|
+
/**
|
|
44
|
+
* Absolute path to the Next.js `app/` directory for automatic route discovery.
|
|
45
|
+
* When set, routes for llms.txt are auto-discovered by scanning page.tsx files.
|
|
46
|
+
* Titles and descriptions are extracted from `export const metadata` in each page.
|
|
47
|
+
*
|
|
48
|
+
* @example
|
|
49
|
+
* ```ts
|
|
50
|
+
* appDir: path.resolve(process.cwd(), 'app')
|
|
51
|
+
* ```
|
|
52
|
+
*/
|
|
53
|
+
appDir?: string;
|
|
54
|
+
/** Options for route discovery when using appDir */
|
|
55
|
+
discoverOptions?: DiscoverOptions;
|
|
56
|
+
}
|
|
57
|
+
declare function createLlmsTxtHandler(options: LlmsTxtHandlerOptions): () => Promise<Response>;
|
|
58
|
+
|
|
59
|
+
export { type LlmsTxtHandlerOptions, type WithAgentSeoOptions, createLlmsTxtHandler, withAgentSeo };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { NextConfig } from 'next';
|
|
2
|
+
import { AgentSeoOptions, DiscoverOptions } from '@agent-seo/core';
|
|
3
|
+
export { AIRequestContext, AgentSeoOptions, BotInfo, BotPurpose, DiscoverOptions, LlmsTxtRoute, TransformResult, discoverNextRoutes, generateLlmsTxt, transform } from '@agent-seo/core';
|
|
4
|
+
export { AgentSeoMiddlewareOptions, createAgentSeoMiddleware } from './middleware.js';
|
|
5
|
+
import 'next/server';
|
|
6
|
+
|
|
7
|
+
interface WithAgentSeoOptions extends AgentSeoOptions {
|
|
8
|
+
/**
|
|
9
|
+
* Absolute path to the Next.js `app/` directory.
|
|
10
|
+
* If omitted, auto-detected by checking `./app` and `./src/app` from cwd.
|
|
11
|
+
*/
|
|
12
|
+
appDir?: string;
|
|
13
|
+
/**
|
|
14
|
+
* Sitemap URL for the `robots.txt` Sitemap directive.
|
|
15
|
+
* - `true`: uses `{baseUrl}/sitemap.xml`
|
|
16
|
+
* - `string`: uses the provided URL
|
|
17
|
+
* - `undefined`/`false`: no Sitemap directive
|
|
18
|
+
*/
|
|
19
|
+
sitemap?: boolean | string;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Next.js config plugin that:
|
|
23
|
+
* 1. Auto-generates `app/llms.txt/route.ts` (zero-config `/llms.txt` endpoint)
|
|
24
|
+
* 2. Injects `Vary: Accept, User-Agent` headers on all pages
|
|
25
|
+
*
|
|
26
|
+
* @example
|
|
27
|
+
* ```ts
|
|
28
|
+
* // next.config.ts
|
|
29
|
+
* import { withAgentSeo } from '@agent-seo/next';
|
|
30
|
+
*
|
|
31
|
+
* export default withAgentSeo({
|
|
32
|
+
* siteName: 'My App',
|
|
33
|
+
* siteDescription: 'A brief description for LLMs.',
|
|
34
|
+
* baseUrl: 'https://myapp.com',
|
|
35
|
+
* })({});
|
|
36
|
+
* ```
|
|
37
|
+
*/
|
|
38
|
+
declare function withAgentSeo(agentSeoOptions: WithAgentSeoOptions): (nextConfig?: NextConfig) => NextConfig;
|
|
39
|
+
|
|
40
|
+
interface LlmsTxtHandlerOptions extends AgentSeoOptions {
|
|
41
|
+
/** Return llms-full.txt instead of llms.txt */
|
|
42
|
+
full?: boolean;
|
|
43
|
+
/**
|
|
44
|
+
* Absolute path to the Next.js `app/` directory for automatic route discovery.
|
|
45
|
+
* When set, routes for llms.txt are auto-discovered by scanning page.tsx files.
|
|
46
|
+
* Titles and descriptions are extracted from `export const metadata` in each page.
|
|
47
|
+
*
|
|
48
|
+
* @example
|
|
49
|
+
* ```ts
|
|
50
|
+
* appDir: path.resolve(process.cwd(), 'app')
|
|
51
|
+
* ```
|
|
52
|
+
*/
|
|
53
|
+
appDir?: string;
|
|
54
|
+
/** Options for route discovery when using appDir */
|
|
55
|
+
discoverOptions?: DiscoverOptions;
|
|
56
|
+
}
|
|
57
|
+
declare function createLlmsTxtHandler(options: LlmsTxtHandlerOptions): () => Promise<Response>;
|
|
58
|
+
|
|
59
|
+
export { type LlmsTxtHandlerOptions, type WithAgentSeoOptions, createLlmsTxtHandler, withAgentSeo };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,312 @@
|
|
|
1
|
+
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
2
|
+
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
3
|
+
}) : x)(function(x) {
|
|
4
|
+
if (typeof require !== "undefined") return require.apply(this, arguments);
|
|
5
|
+
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
6
|
+
});
|
|
7
|
+
|
|
8
|
+
// src/plugin.ts
|
|
9
|
+
import { existsSync, mkdirSync, writeFileSync } from "fs";
|
|
10
|
+
import { join } from "path";
|
|
11
|
+
function withAgentSeo(agentSeoOptions) {
|
|
12
|
+
const appDir = agentSeoOptions.appDir || detectAppDir();
|
|
13
|
+
if (appDir) {
|
|
14
|
+
generateLlmsTxtRoute(appDir, agentSeoOptions);
|
|
15
|
+
generateTransformRoute(appDir, agentSeoOptions);
|
|
16
|
+
generateRobotsTxt(appDir, agentSeoOptions);
|
|
17
|
+
}
|
|
18
|
+
return (nextConfig = {}) => {
|
|
19
|
+
return {
|
|
20
|
+
...nextConfig,
|
|
21
|
+
async headers() {
|
|
22
|
+
const existingHeaders = await (nextConfig.headers?.() ?? []);
|
|
23
|
+
return [
|
|
24
|
+
...existingHeaders,
|
|
25
|
+
{
|
|
26
|
+
source: "/((?!api|_next|static|favicon.ico).*)",
|
|
27
|
+
headers: [{ key: "Vary", value: "Accept, User-Agent" }]
|
|
28
|
+
}
|
|
29
|
+
];
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
function detectAppDir() {
|
|
35
|
+
const cwd = process.cwd();
|
|
36
|
+
const candidates = [join(cwd, "app"), join(cwd, "src", "app")];
|
|
37
|
+
for (const candidate of candidates) {
|
|
38
|
+
if (existsSync(candidate)) {
|
|
39
|
+
return candidate;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
var GENERATED_BANNER = `// AUTO-GENERATED by @agent-seo/next \u2014 do not edit manually.
|
|
45
|
+
// This file is created by withAgentSeo() in next.config.ts.
|
|
46
|
+
// Add "app/llms.txt" to your .gitignore.
|
|
47
|
+
`;
|
|
48
|
+
function generateLlmsTxtRoute(appDir, options) {
|
|
49
|
+
const routeDir = join(appDir, "llms.txt");
|
|
50
|
+
const routeFile = join(routeDir, "route.js");
|
|
51
|
+
const siteName = escapeStr(options.siteName);
|
|
52
|
+
const siteDescription = escapeStr(options.siteDescription);
|
|
53
|
+
const baseUrl = escapeStr(options.baseUrl);
|
|
54
|
+
const excludePatterns = JSON.stringify(options.exclude || ["/api"]);
|
|
55
|
+
const content = `${GENERATED_BANNER}
|
|
56
|
+
import { generateLlmsTxt, discoverNextRoutes } from '@agent-seo/next';
|
|
57
|
+
import path from 'node:path';
|
|
58
|
+
|
|
59
|
+
const appDir = path.resolve(process.cwd(), 'app');
|
|
60
|
+
|
|
61
|
+
export async function GET(request) {
|
|
62
|
+
const url = new URL(request.url);
|
|
63
|
+
|
|
64
|
+
const routes = discoverNextRoutes(appDir, {
|
|
65
|
+
exclude: ${excludePatterns},
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
const result = generateLlmsTxt(
|
|
69
|
+
{
|
|
70
|
+
siteName: '${siteName}',
|
|
71
|
+
siteDescription: '${siteDescription}',
|
|
72
|
+
baseUrl: process.env.NEXT_PUBLIC_BASE_URL || '${baseUrl}',
|
|
73
|
+
},
|
|
74
|
+
routes,
|
|
75
|
+
);
|
|
76
|
+
|
|
77
|
+
return new Response(result.llmsTxt, {
|
|
78
|
+
status: 200,
|
|
79
|
+
headers: {
|
|
80
|
+
'Content-Type': 'text/plain; charset=utf-8',
|
|
81
|
+
'Content-Disposition': 'inline',
|
|
82
|
+
'Cache-Control': 'public, max-age=3600, s-maxage=3600',
|
|
83
|
+
},
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
`;
|
|
87
|
+
mkdirSync(routeDir, { recursive: true });
|
|
88
|
+
let existingContent = "";
|
|
89
|
+
try {
|
|
90
|
+
const { readFileSync } = __require("fs");
|
|
91
|
+
existingContent = readFileSync(routeFile, "utf-8");
|
|
92
|
+
} catch {
|
|
93
|
+
}
|
|
94
|
+
if (existingContent !== content) {
|
|
95
|
+
writeFileSync(routeFile, content, "utf-8");
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
function escapeStr(s) {
|
|
99
|
+
return s.replace(/\\/g, "\\\\").replace(/'/g, "\\'");
|
|
100
|
+
}
|
|
101
|
+
function generateTransformRoute(appDir, options) {
|
|
102
|
+
const routeDir = join(appDir, "api", "agent-seo-transform");
|
|
103
|
+
const routeFile = join(routeDir, "route.js");
|
|
104
|
+
const baseUrl = escapeStr(options.baseUrl);
|
|
105
|
+
const content = `${GENERATED_BANNER}
|
|
106
|
+
import { transform } from '@agent-seo/next';
|
|
107
|
+
|
|
108
|
+
// Force Node.js runtime (required for jsdom / Readability / Turndown)
|
|
109
|
+
export const runtime = 'nodejs';
|
|
110
|
+
export const dynamic = 'force-dynamic';
|
|
111
|
+
|
|
112
|
+
export async function GET(request) {
|
|
113
|
+
const { searchParams } = new URL(request.url);
|
|
114
|
+
const pagePath = searchParams.get('path') || '/';
|
|
115
|
+
|
|
116
|
+
// Build the internal URL to fetch the original HTML page
|
|
117
|
+
const origin = process.env.NEXT_PUBLIC_BASE_URL || '${baseUrl}';
|
|
118
|
+
if (!origin) {
|
|
119
|
+
return new Response('Base URL not configured', { status: 500 });
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Only allow same-origin, absolute paths (prevent SSRF via absolute or protocol-relative URLs)
|
|
123
|
+
const isValidPath =
|
|
124
|
+
pagePath.startsWith('/') &&
|
|
125
|
+
!pagePath.startsWith('//') &&
|
|
126
|
+
!pagePath.includes('://') &&
|
|
127
|
+
!pagePath.includes('\\\\');
|
|
128
|
+
if (!isValidPath) {
|
|
129
|
+
return new Response('Invalid path', { status: 400 });
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const originUrl = new URL(origin);
|
|
133
|
+
if (originUrl.protocol !== 'http:' && originUrl.protocol !== 'https:') {
|
|
134
|
+
return new Response('Invalid base URL', { status: 500 });
|
|
135
|
+
}
|
|
136
|
+
const pageUrl = new URL(pagePath, originUrl.origin);
|
|
137
|
+
if (pageUrl.origin !== originUrl.origin) {
|
|
138
|
+
return new Response('Invalid origin', { status: 400 });
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
try {
|
|
142
|
+
// Fetch the page HTML from the local server with a normal User-Agent
|
|
143
|
+
// to avoid infinite rewrite loops
|
|
144
|
+
const res = await fetch(pageUrl.toString(), {
|
|
145
|
+
headers: {
|
|
146
|
+
'User-Agent': 'AgentSEO-Internal/1.0',
|
|
147
|
+
'Accept': 'text/html',
|
|
148
|
+
},
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
if (!res.ok) {
|
|
152
|
+
return new Response('Page not found', { status: 404 });
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const html = await res.text();
|
|
156
|
+
|
|
157
|
+
const result = await transform(html, {
|
|
158
|
+
url: pageUrl.toString(),
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
return new Response(result.markdown, {
|
|
162
|
+
status: 200,
|
|
163
|
+
headers: {
|
|
164
|
+
'Content-Type': 'text/markdown; charset=utf-8',
|
|
165
|
+
'Content-Disposition': 'inline',
|
|
166
|
+
'Cache-Control': 'public, max-age=3600, s-maxage=3600',
|
|
167
|
+
'Vary': 'Accept, User-Agent',
|
|
168
|
+
'X-Robots-Tag': 'all',
|
|
169
|
+
'X-Agent-Seo': 'transformed',
|
|
170
|
+
},
|
|
171
|
+
});
|
|
172
|
+
} catch (err) {
|
|
173
|
+
console.error('[agent-seo] Transform error:', err);
|
|
174
|
+
return new Response('Transform failed', { status: 500 });
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
`;
|
|
178
|
+
mkdirSync(routeDir, { recursive: true });
|
|
179
|
+
let existingContent = "";
|
|
180
|
+
try {
|
|
181
|
+
const { readFileSync } = __require("fs");
|
|
182
|
+
existingContent = readFileSync(routeFile, "utf-8");
|
|
183
|
+
} catch {
|
|
184
|
+
}
|
|
185
|
+
if (existingContent !== content) {
|
|
186
|
+
writeFileSync(routeFile, content, "utf-8");
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
function generateRobotsTxt(appDir, options) {
|
|
190
|
+
const robotsFile = join(appDir, "robots.txt");
|
|
191
|
+
if (existsSync(robotsFile)) {
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
let content = `# AI-Optimized robots.txt \u2014 Generated by @agent-seo/next
|
|
195
|
+
# For AI agents: see /llms.txt for a structured site manifest
|
|
196
|
+
|
|
197
|
+
User-agent: *
|
|
198
|
+
Allow: /
|
|
199
|
+
`;
|
|
200
|
+
if (options.sitemap) {
|
|
201
|
+
const sitemapUrl = typeof options.sitemap === "string" ? options.sitemap : `${options.baseUrl}/sitemap.xml`;
|
|
202
|
+
content += `
|
|
203
|
+
Sitemap: ${sitemapUrl}
|
|
204
|
+
`;
|
|
205
|
+
}
|
|
206
|
+
writeFileSync(robotsFile, content, "utf-8");
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// src/middleware.ts
|
|
210
|
+
import { detectAgent } from "@agent-seo/core/edge";
|
|
211
|
+
import { NextResponse } from "next/server";
|
|
212
|
+
var ALWAYS_SKIP = /* @__PURE__ */ new Set([
|
|
213
|
+
"/favicon.ico",
|
|
214
|
+
"/robots.txt",
|
|
215
|
+
"/sitemap.xml",
|
|
216
|
+
"/llms.txt",
|
|
217
|
+
"/llms-full.txt"
|
|
218
|
+
]);
|
|
219
|
+
var DEFAULT_EXCLUDE = [
|
|
220
|
+
"/api/**",
|
|
221
|
+
"/_next/**"
|
|
222
|
+
];
|
|
223
|
+
function createAgentSeoMiddleware(options = {}) {
|
|
224
|
+
const excludePatterns = [...DEFAULT_EXCLUDE, ...options.exclude || []];
|
|
225
|
+
return function middleware(request) {
|
|
226
|
+
const { pathname } = request.nextUrl;
|
|
227
|
+
if (ALWAYS_SKIP.has(pathname)) {
|
|
228
|
+
return NextResponse.next();
|
|
229
|
+
}
|
|
230
|
+
if (isExcluded(pathname, excludePatterns)) {
|
|
231
|
+
return NextResponse.next();
|
|
232
|
+
}
|
|
233
|
+
const ua = request.headers.get("user-agent");
|
|
234
|
+
const accept = request.headers.get("accept");
|
|
235
|
+
const aiCtx = detectAgent(ua, accept);
|
|
236
|
+
if (pathname.endsWith(".md")) {
|
|
237
|
+
const originalPath = pathname.slice(0, -3) || "/";
|
|
238
|
+
const transformUrl = new URL("/api/agent-seo-transform", request.url);
|
|
239
|
+
transformUrl.searchParams.set("path", originalPath);
|
|
240
|
+
return setBotHeaders(NextResponse.rewrite(transformUrl));
|
|
241
|
+
}
|
|
242
|
+
if (aiCtx.isAIBot) {
|
|
243
|
+
const transformUrl = new URL("/api/agent-seo-transform", request.url);
|
|
244
|
+
transformUrl.searchParams.set("path", pathname);
|
|
245
|
+
return setBotHeaders(NextResponse.rewrite(transformUrl));
|
|
246
|
+
}
|
|
247
|
+
const response = NextResponse.next();
|
|
248
|
+
response.headers.set("Vary", "Accept, User-Agent");
|
|
249
|
+
return response;
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
function setBotHeaders(response) {
|
|
253
|
+
response.headers.set("Content-Disposition", "inline");
|
|
254
|
+
response.headers.set("Vary", "Accept, User-Agent");
|
|
255
|
+
response.headers.set("X-Robots-Tag", "all");
|
|
256
|
+
response.headers.delete("x-nextjs-matched-path");
|
|
257
|
+
return response;
|
|
258
|
+
}
|
|
259
|
+
function isExcluded(path, patterns) {
|
|
260
|
+
return patterns.some((pattern) => matchGlob(pattern, path));
|
|
261
|
+
}
|
|
262
|
+
function matchGlob(pattern, path) {
|
|
263
|
+
const regex = pattern.replace(/\*\*/g, "{{DOUBLESTAR}}").replace(/\*/g, "[^/]*").replace(/{{DOUBLESTAR}}/g, ".*");
|
|
264
|
+
return new RegExp(`^${regex}$`).test(path);
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// src/route-handler.ts
|
|
268
|
+
import { generateLlmsTxt, discoverNextRoutes } from "@agent-seo/core";
|
|
269
|
+
function createLlmsTxtHandler(options) {
|
|
270
|
+
return async function GET() {
|
|
271
|
+
let routes = options.llmsTxt?.routes || [];
|
|
272
|
+
if (routes.length === 0 && options.appDir) {
|
|
273
|
+
routes = discoverNextRoutes(options.appDir, {
|
|
274
|
+
exclude: options.exclude || ["/api"],
|
|
275
|
+
...options.discoverOptions
|
|
276
|
+
});
|
|
277
|
+
}
|
|
278
|
+
const result = generateLlmsTxt(
|
|
279
|
+
{
|
|
280
|
+
siteName: options.siteName,
|
|
281
|
+
siteDescription: options.siteDescription,
|
|
282
|
+
baseUrl: options.baseUrl,
|
|
283
|
+
...options.llmsTxt
|
|
284
|
+
},
|
|
285
|
+
routes
|
|
286
|
+
);
|
|
287
|
+
const content = options.full ? result.llmsFullTxt : result.llmsTxt;
|
|
288
|
+
return new Response(content, {
|
|
289
|
+
status: 200,
|
|
290
|
+
headers: {
|
|
291
|
+
"Content-Type": "text/plain; charset=utf-8",
|
|
292
|
+
"Cache-Control": "public, max-age=3600, s-maxage=3600"
|
|
293
|
+
}
|
|
294
|
+
});
|
|
295
|
+
};
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
// src/index.ts
|
|
299
|
+
import {
|
|
300
|
+
generateLlmsTxt as generateLlmsTxt2,
|
|
301
|
+
discoverNextRoutes as discoverNextRoutes2,
|
|
302
|
+
transform
|
|
303
|
+
} from "@agent-seo/core";
|
|
304
|
+
export {
|
|
305
|
+
createAgentSeoMiddleware,
|
|
306
|
+
createLlmsTxtHandler,
|
|
307
|
+
discoverNextRoutes2 as discoverNextRoutes,
|
|
308
|
+
generateLlmsTxt2 as generateLlmsTxt,
|
|
309
|
+
transform,
|
|
310
|
+
withAgentSeo
|
|
311
|
+
};
|
|
312
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/plugin.ts","../src/middleware.ts","../src/route-handler.ts","../src/index.ts"],"sourcesContent":["import { existsSync, mkdirSync, writeFileSync } from 'node:fs';\nimport { join, resolve } from 'node:path';\nimport type { NextConfig } from 'next';\nimport type { AgentSeoOptions } from '@agent-seo/core';\n\nexport interface WithAgentSeoOptions extends AgentSeoOptions {\n /**\n * Absolute path to the Next.js `app/` directory.\n * If omitted, auto-detected by checking `./app` and `./src/app` from cwd.\n */\n appDir?: string;\n /**\n * Sitemap URL for the `robots.txt` Sitemap directive.\n * - `true`: uses `{baseUrl}/sitemap.xml`\n * - `string`: uses the provided URL\n * - `undefined`/`false`: no Sitemap directive\n */\n sitemap?: boolean | string;\n}\n\n/**\n * Next.js config plugin that:\n * 1. Auto-generates `app/llms.txt/route.ts` (zero-config `/llms.txt` endpoint)\n * 2. Injects `Vary: Accept, User-Agent` headers on all pages\n *\n * @example\n * ```ts\n * // next.config.ts\n * import { withAgentSeo } from '@agent-seo/next';\n *\n * export default withAgentSeo({\n * siteName: 'My App',\n * siteDescription: 'A brief description for LLMs.',\n * baseUrl: 'https://myapp.com',\n * })({});\n * ```\n */\nexport function withAgentSeo(agentSeoOptions: WithAgentSeoOptions) {\n const appDir = agentSeoOptions.appDir || detectAppDir();\n\n // Auto-generate route handlers at config-evaluation time\n if (appDir) {\n generateLlmsTxtRoute(appDir, agentSeoOptions);\n generateTransformRoute(appDir, agentSeoOptions);\n generateRobotsTxt(appDir, agentSeoOptions);\n }\n\n return (nextConfig: NextConfig = {}): NextConfig => {\n return {\n ...nextConfig,\n\n async headers() {\n const existingHeaders = await (nextConfig.headers?.() ?? []);\n return [\n ...existingHeaders,\n {\n source: '/((?!api|_next|static|favicon.ico).*)',\n headers: [{ key: 'Vary', value: 'Accept, User-Agent' }],\n },\n ];\n },\n };\n };\n}\n\n/**\n * Auto-detect the Next.js `app/` directory from common locations.\n */\nfunction detectAppDir(): string | null {\n const cwd = process.cwd();\n const candidates = [join(cwd, 'app'), join(cwd, 'src', 'app')];\n\n for (const candidate of candidates) {\n if (existsSync(candidate)) {\n return candidate;\n }\n }\n\n return null;\n}\n\n// Auto-generated file banner\nconst GENERATED_BANNER = `// AUTO-GENERATED by @agent-seo/next — do not edit manually.\n// This file is created by withAgentSeo() in next.config.ts.\n// Add \"app/llms.txt\" to your .gitignore.\n`;\n\n/**\n * Write `app/llms.txt/route.js` with a handler that auto-discovers routes.\n */\nfunction generateLlmsTxtRoute(\n appDir: string,\n options: WithAgentSeoOptions,\n): void {\n const routeDir = join(appDir, 'llms.txt');\n const routeFile = join(routeDir, 'route.js');\n\n // Serialize the options we need into the generated file\n const siteName = escapeStr(options.siteName);\n const siteDescription = escapeStr(options.siteDescription);\n const baseUrl = escapeStr(options.baseUrl);\n const excludePatterns = JSON.stringify(options.exclude || ['/api']);\n\n const content = `${GENERATED_BANNER}\nimport { generateLlmsTxt, discoverNextRoutes } from '@agent-seo/next';\nimport path from 'node:path';\n\nconst appDir = path.resolve(process.cwd(), 'app');\n\nexport async function GET(request) {\n const url = new URL(request.url);\n\n const routes = discoverNextRoutes(appDir, {\n exclude: ${excludePatterns},\n });\n\n const result = generateLlmsTxt(\n {\n siteName: '${siteName}',\n siteDescription: '${siteDescription}',\n baseUrl: process.env.NEXT_PUBLIC_BASE_URL || '${baseUrl}',\n },\n routes,\n );\n\n return new Response(result.llmsTxt, {\n status: 200,\n headers: {\n 'Content-Type': 'text/plain; charset=utf-8',\n 'Content-Disposition': 'inline',\n 'Cache-Control': 'public, max-age=3600, s-maxage=3600',\n },\n });\n}\n`;\n\n // Only write if the file doesn't exist or content has changed\n mkdirSync(routeDir, { recursive: true });\n\n let existingContent = '';\n try {\n const { readFileSync } = require('node:fs');\n existingContent = readFileSync(routeFile, 'utf-8');\n } catch {\n // File doesn't exist yet\n }\n\n if (existingContent !== content) {\n writeFileSync(routeFile, content, 'utf-8');\n }\n}\n\nfunction escapeStr(s: string): string {\n return s.replace(/\\\\/g, '\\\\\\\\').replace(/'/g, \"\\\\'\");\n}\n\n/**\n * Write `app/api/agent-seo-transform/route.js` — a Node.js API route that\n * receives a `?path=/some-page` param, fetches the HTML from the local\n * Next.js server, runs the core `transform()` pipeline (JSDOM → Readability\n * → Turndown), and returns clean Markdown.\n */\nfunction generateTransformRoute(\n appDir: string,\n options: WithAgentSeoOptions,\n): void {\n const routeDir = join(appDir, 'api', 'agent-seo-transform');\n const routeFile = join(routeDir, 'route.js');\n\n const baseUrl = escapeStr(options.baseUrl);\n\n const content = `${GENERATED_BANNER}\nimport { transform } from '@agent-seo/next';\n\n// Force Node.js runtime (required for jsdom / Readability / Turndown)\nexport const runtime = 'nodejs';\nexport const dynamic = 'force-dynamic';\n\nexport async function GET(request) {\n const { searchParams } = new URL(request.url);\n const pagePath = searchParams.get('path') || '/';\n\n // Build the internal URL to fetch the original HTML page\n const origin = process.env.NEXT_PUBLIC_BASE_URL || '${baseUrl}';\n if (!origin) {\n return new Response('Base URL not configured', { status: 500 });\n }\n\n // Only allow same-origin, absolute paths (prevent SSRF via absolute or protocol-relative URLs)\n const isValidPath =\n pagePath.startsWith('/') &&\n !pagePath.startsWith('//') &&\n !pagePath.includes('://') &&\n !pagePath.includes('\\\\\\\\');\n if (!isValidPath) {\n return new Response('Invalid path', { status: 400 });\n }\n\n const originUrl = new URL(origin);\n if (originUrl.protocol !== 'http:' && originUrl.protocol !== 'https:') {\n return new Response('Invalid base URL', { status: 500 });\n }\n const pageUrl = new URL(pagePath, originUrl.origin);\n if (pageUrl.origin !== originUrl.origin) {\n return new Response('Invalid origin', { status: 400 });\n }\n\n try {\n // Fetch the page HTML from the local server with a normal User-Agent\n // to avoid infinite rewrite loops\n const res = await fetch(pageUrl.toString(), {\n headers: {\n 'User-Agent': 'AgentSEO-Internal/1.0',\n 'Accept': 'text/html',\n },\n });\n\n if (!res.ok) {\n return new Response('Page not found', { status: 404 });\n }\n\n const html = await res.text();\n\n const result = await transform(html, {\n url: pageUrl.toString(),\n });\n\n return new Response(result.markdown, {\n status: 200,\n headers: {\n 'Content-Type': 'text/markdown; charset=utf-8',\n 'Content-Disposition': 'inline',\n 'Cache-Control': 'public, max-age=3600, s-maxage=3600',\n 'Vary': 'Accept, User-Agent',\n 'X-Robots-Tag': 'all',\n 'X-Agent-Seo': 'transformed',\n },\n });\n } catch (err) {\n console.error('[agent-seo] Transform error:', err);\n return new Response('Transform failed', { status: 500 });\n }\n}\n`;\n\n mkdirSync(routeDir, { recursive: true });\n\n let existingContent = '';\n try {\n const { readFileSync } = require('node:fs');\n existingContent = readFileSync(routeFile, 'utf-8');\n } catch {\n // File doesn't exist yet\n }\n\n if (existingContent !== content) {\n writeFileSync(routeFile, content, 'utf-8');\n }\n}\n\n/**\n * Write `app/robots.txt` — a static robots.txt that allows all crawlers\n * and references /llms.txt for AI agents.\n * Only created if the file doesn't already exist (won't overwrite user customizations).\n */\nfunction generateRobotsTxt(\n appDir: string,\n options: WithAgentSeoOptions,\n): void {\n const robotsFile = join(appDir, 'robots.txt');\n\n // Don't overwrite user-created robots.txt\n if (existsSync(robotsFile)) {\n return;\n }\n\n let content = `# AI-Optimized robots.txt — Generated by @agent-seo/next\n# For AI agents: see /llms.txt for a structured site manifest\n\nUser-agent: *\nAllow: /\n`;\n\n if (options.sitemap) {\n const sitemapUrl =\n typeof options.sitemap === 'string'\n ? options.sitemap\n : `${options.baseUrl}/sitemap.xml`;\n content += `\\nSitemap: ${sitemapUrl}\\n`;\n }\n\n writeFileSync(robotsFile, content, 'utf-8');\n}\n","import { detectAgent } from '@agent-seo/core/edge';\nimport { NextResponse } from 'next/server';\nimport type { NextRequest } from 'next/server';\n\nexport interface AgentSeoMiddlewareOptions {\n /**\n * Glob patterns for paths that should NEVER be rewritten to Markdown.\n * Merged with built-in defaults (see `DEFAULT_EXCLUDE`).\n *\n * @example ['\\/dashboard\\/**', '\\/admin\\/**', '\\/api\\/private\\/**']\n */\n exclude?: string[];\n}\n\n/** Paths that are always skipped — framework internals + standard files. */\nconst ALWAYS_SKIP = new Set([\n '/favicon.ico',\n '/robots.txt',\n '/sitemap.xml',\n '/llms.txt',\n '/llms-full.txt',\n]);\n\n/** Default exclude patterns — users can extend but not remove these. */\nconst DEFAULT_EXCLUDE: string[] = [\n '/api/**',\n '/_next/**',\n];\n\n/**\n * Creates a Next.js middleware that:\n * 1. Detects AI bot requests via User-Agent\n * 2. Rewrites AI bot requests to an internal transform API route\n * that converts HTML → Markdown (runs on Node.js runtime)\n * 3. Handles `.md` suffix requests (e.g. `/about.md` → transform `/about`)\n * 4. Sets `Vary: Accept, User-Agent` on all responses\n */\nexport function createAgentSeoMiddleware(options: AgentSeoMiddlewareOptions = {}) {\n const excludePatterns = [...DEFAULT_EXCLUDE, ...(options.exclude || [])];\n\n return function middleware(request: NextRequest) {\n const { pathname } = request.nextUrl;\n\n // Skip standard files (robots.txt, favicon, llms.txt, etc.)\n if (ALWAYS_SKIP.has(pathname)) {\n return NextResponse.next();\n }\n\n // Skip excluded patterns (API routes, admin, dashboard, etc.)\n if (isExcluded(pathname, excludePatterns)) {\n return NextResponse.next();\n }\n\n const ua = request.headers.get('user-agent');\n const accept = request.headers.get('accept');\n const aiCtx = detectAgent(ua, accept);\n\n // Handle explicit .md suffix requests (e.g. /about.md)\n if (pathname.endsWith('.md')) {\n const originalPath = pathname.slice(0, -3) || '/';\n const transformUrl = new URL('/api/agent-seo-transform', request.url);\n transformUrl.searchParams.set('path', originalPath);\n return setBotHeaders(NextResponse.rewrite(transformUrl));\n }\n\n // If AI bot, rewrite to transform API\n if (aiCtx.isAIBot) {\n const transformUrl = new URL('/api/agent-seo-transform', request.url);\n transformUrl.searchParams.set('path', pathname);\n return setBotHeaders(NextResponse.rewrite(transformUrl));\n }\n\n // Normal request — just add Vary header\n const response = NextResponse.next();\n response.headers.set('Vary', 'Accept, User-Agent');\n return response;\n };\n}\n\n/**\n * Set clean, bot-friendly headers on a rewrite response.\n * Overrides Next.js RSC-related headers that can confuse AI bots.\n */\nfunction setBotHeaders(response: NextResponse): NextResponse {\n response.headers.set('Content-Disposition', 'inline');\n response.headers.set('Vary', 'Accept, User-Agent');\n response.headers.set('X-Robots-Tag', 'all');\n response.headers.delete('x-nextjs-matched-path');\n return response;\n}\n\nfunction isExcluded(path: string, patterns: string[]): boolean {\n return patterns.some((pattern) => matchGlob(pattern, path));\n}\n\nfunction matchGlob(pattern: string, path: string): boolean {\n const regex = pattern\n .replace(/\\*\\*/g, '{{DOUBLESTAR}}')\n .replace(/\\*/g, '[^/]*')\n .replace(/{{DOUBLESTAR}}/g, '.*');\n return new RegExp(`^${regex}$`).test(path);\n}\n","import { generateLlmsTxt, discoverNextRoutes } from '@agent-seo/core';\nimport type { AgentSeoOptions, DiscoverOptions } from '@agent-seo/core';\n\nexport interface LlmsTxtHandlerOptions extends AgentSeoOptions {\n /** Return llms-full.txt instead of llms.txt */\n full?: boolean;\n\n /**\n * Absolute path to the Next.js `app/` directory for automatic route discovery.\n * When set, routes for llms.txt are auto-discovered by scanning page.tsx files.\n * Titles and descriptions are extracted from `export const metadata` in each page.\n *\n * @example\n * ```ts\n * appDir: path.resolve(process.cwd(), 'app')\n * ```\n */\n appDir?: string;\n\n /** Options for route discovery when using appDir */\n discoverOptions?: DiscoverOptions;\n}\n\nexport function createLlmsTxtHandler(options: LlmsTxtHandlerOptions) {\n return async function GET() {\n // Auto-discover routes from app/ directory if no explicit routes are provided\n let routes = options.llmsTxt?.routes || [];\n\n if (routes.length === 0 && options.appDir) {\n routes = discoverNextRoutes(options.appDir, {\n exclude: options.exclude || ['/api'],\n ...options.discoverOptions,\n });\n }\n\n const result = generateLlmsTxt(\n {\n siteName: options.siteName,\n siteDescription: options.siteDescription,\n baseUrl: options.baseUrl,\n ...options.llmsTxt,\n },\n routes,\n );\n\n const content = options.full ? result.llmsFullTxt : result.llmsTxt;\n\n return new Response(content, {\n status: 200,\n headers: {\n 'Content-Type': 'text/plain; charset=utf-8',\n 'Cache-Control': 'public, max-age=3600, s-maxage=3600',\n },\n });\n };\n}\n","export { withAgentSeo } from './plugin.js';\nexport type { WithAgentSeoOptions } from './plugin.js';\nexport { createAgentSeoMiddleware } from './middleware.js';\nexport type { AgentSeoMiddlewareOptions } from './middleware.js';\nexport { createLlmsTxtHandler } from './route-handler.js';\nexport type { LlmsTxtHandlerOptions } from './route-handler.js';\nexport {\n generateLlmsTxt,\n discoverNextRoutes,\n transform,\n} from '@agent-seo/core';\nexport type {\n AgentSeoOptions,\n AIRequestContext,\n TransformResult,\n BotInfo,\n BotPurpose,\n LlmsTxtRoute,\n DiscoverOptions,\n} from '@agent-seo/core';\n"],"mappings":"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","names":["generateLlmsTxt","discoverNextRoutes"]}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/middleware.ts
|
|
21
|
+
var middleware_exports = {};
|
|
22
|
+
__export(middleware_exports, {
|
|
23
|
+
createAgentSeoMiddleware: () => createAgentSeoMiddleware
|
|
24
|
+
});
|
|
25
|
+
module.exports = __toCommonJS(middleware_exports);
|
|
26
|
+
var import_edge = require("@agent-seo/core/edge");
|
|
27
|
+
var import_server = require("next/server");
|
|
28
|
+
var ALWAYS_SKIP = /* @__PURE__ */ new Set([
|
|
29
|
+
"/favicon.ico",
|
|
30
|
+
"/robots.txt",
|
|
31
|
+
"/sitemap.xml",
|
|
32
|
+
"/llms.txt",
|
|
33
|
+
"/llms-full.txt"
|
|
34
|
+
]);
|
|
35
|
+
var DEFAULT_EXCLUDE = [
|
|
36
|
+
"/api/**",
|
|
37
|
+
"/_next/**"
|
|
38
|
+
];
|
|
39
|
+
function createAgentSeoMiddleware(options = {}) {
|
|
40
|
+
const excludePatterns = [...DEFAULT_EXCLUDE, ...options.exclude || []];
|
|
41
|
+
return function middleware(request) {
|
|
42
|
+
const { pathname } = request.nextUrl;
|
|
43
|
+
if (ALWAYS_SKIP.has(pathname)) {
|
|
44
|
+
return import_server.NextResponse.next();
|
|
45
|
+
}
|
|
46
|
+
if (isExcluded(pathname, excludePatterns)) {
|
|
47
|
+
return import_server.NextResponse.next();
|
|
48
|
+
}
|
|
49
|
+
const ua = request.headers.get("user-agent");
|
|
50
|
+
const accept = request.headers.get("accept");
|
|
51
|
+
const aiCtx = (0, import_edge.detectAgent)(ua, accept);
|
|
52
|
+
if (pathname.endsWith(".md")) {
|
|
53
|
+
const originalPath = pathname.slice(0, -3) || "/";
|
|
54
|
+
const transformUrl = new URL("/api/agent-seo-transform", request.url);
|
|
55
|
+
transformUrl.searchParams.set("path", originalPath);
|
|
56
|
+
return setBotHeaders(import_server.NextResponse.rewrite(transformUrl));
|
|
57
|
+
}
|
|
58
|
+
if (aiCtx.isAIBot) {
|
|
59
|
+
const transformUrl = new URL("/api/agent-seo-transform", request.url);
|
|
60
|
+
transformUrl.searchParams.set("path", pathname);
|
|
61
|
+
return setBotHeaders(import_server.NextResponse.rewrite(transformUrl));
|
|
62
|
+
}
|
|
63
|
+
const response = import_server.NextResponse.next();
|
|
64
|
+
response.headers.set("Vary", "Accept, User-Agent");
|
|
65
|
+
return response;
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
function setBotHeaders(response) {
|
|
69
|
+
response.headers.set("Content-Disposition", "inline");
|
|
70
|
+
response.headers.set("Vary", "Accept, User-Agent");
|
|
71
|
+
response.headers.set("X-Robots-Tag", "all");
|
|
72
|
+
response.headers.delete("x-nextjs-matched-path");
|
|
73
|
+
return response;
|
|
74
|
+
}
|
|
75
|
+
function isExcluded(path, patterns) {
|
|
76
|
+
return patterns.some((pattern) => matchGlob(pattern, path));
|
|
77
|
+
}
|
|
78
|
+
function matchGlob(pattern, path) {
|
|
79
|
+
const regex = pattern.replace(/\*\*/g, "{{DOUBLESTAR}}").replace(/\*/g, "[^/]*").replace(/{{DOUBLESTAR}}/g, ".*");
|
|
80
|
+
return new RegExp(`^${regex}$`).test(path);
|
|
81
|
+
}
|
|
82
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
83
|
+
0 && (module.exports = {
|
|
84
|
+
createAgentSeoMiddleware
|
|
85
|
+
});
|
|
86
|
+
//# sourceMappingURL=middleware.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/middleware.ts"],"sourcesContent":["import { detectAgent } from '@agent-seo/core/edge';\nimport { NextResponse } from 'next/server';\nimport type { NextRequest } from 'next/server';\n\nexport interface AgentSeoMiddlewareOptions {\n /**\n * Glob patterns for paths that should NEVER be rewritten to Markdown.\n * Merged with built-in defaults (see `DEFAULT_EXCLUDE`).\n *\n * @example ['\\/dashboard\\/**', '\\/admin\\/**', '\\/api\\/private\\/**']\n */\n exclude?: string[];\n}\n\n/** Paths that are always skipped — framework internals + standard files. */\nconst ALWAYS_SKIP = new Set([\n '/favicon.ico',\n '/robots.txt',\n '/sitemap.xml',\n '/llms.txt',\n '/llms-full.txt',\n]);\n\n/** Default exclude patterns — users can extend but not remove these. */\nconst DEFAULT_EXCLUDE: string[] = [\n '/api/**',\n '/_next/**',\n];\n\n/**\n * Creates a Next.js middleware that:\n * 1. Detects AI bot requests via User-Agent\n * 2. Rewrites AI bot requests to an internal transform API route\n * that converts HTML → Markdown (runs on Node.js runtime)\n * 3. Handles `.md` suffix requests (e.g. `/about.md` → transform `/about`)\n * 4. Sets `Vary: Accept, User-Agent` on all responses\n */\nexport function createAgentSeoMiddleware(options: AgentSeoMiddlewareOptions = {}) {\n const excludePatterns = [...DEFAULT_EXCLUDE, ...(options.exclude || [])];\n\n return function middleware(request: NextRequest) {\n const { pathname } = request.nextUrl;\n\n // Skip standard files (robots.txt, favicon, llms.txt, etc.)\n if (ALWAYS_SKIP.has(pathname)) {\n return NextResponse.next();\n }\n\n // Skip excluded patterns (API routes, admin, dashboard, etc.)\n if (isExcluded(pathname, excludePatterns)) {\n return NextResponse.next();\n }\n\n const ua = request.headers.get('user-agent');\n const accept = request.headers.get('accept');\n const aiCtx = detectAgent(ua, accept);\n\n // Handle explicit .md suffix requests (e.g. /about.md)\n if (pathname.endsWith('.md')) {\n const originalPath = pathname.slice(0, -3) || '/';\n const transformUrl = new URL('/api/agent-seo-transform', request.url);\n transformUrl.searchParams.set('path', originalPath);\n return setBotHeaders(NextResponse.rewrite(transformUrl));\n }\n\n // If AI bot, rewrite to transform API\n if (aiCtx.isAIBot) {\n const transformUrl = new URL('/api/agent-seo-transform', request.url);\n transformUrl.searchParams.set('path', pathname);\n return setBotHeaders(NextResponse.rewrite(transformUrl));\n }\n\n // Normal request — just add Vary header\n const response = NextResponse.next();\n response.headers.set('Vary', 'Accept, User-Agent');\n return response;\n };\n}\n\n/**\n * Set clean, bot-friendly headers on a rewrite response.\n * Overrides Next.js RSC-related headers that can confuse AI bots.\n */\nfunction setBotHeaders(response: NextResponse): NextResponse {\n response.headers.set('Content-Disposition', 'inline');\n response.headers.set('Vary', 'Accept, User-Agent');\n response.headers.set('X-Robots-Tag', 'all');\n response.headers.delete('x-nextjs-matched-path');\n return response;\n}\n\nfunction isExcluded(path: string, patterns: string[]): boolean {\n return patterns.some((pattern) => matchGlob(pattern, path));\n}\n\nfunction matchGlob(pattern: string, path: string): boolean {\n const regex = pattern\n .replace(/\\*\\*/g, '{{DOUBLESTAR}}')\n .replace(/\\*/g, '[^/]*')\n .replace(/{{DOUBLESTAR}}/g, '.*');\n return new RegExp(`^${regex}$`).test(path);\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,kBAA4B;AAC5B,oBAA6B;AAc7B,IAAM,cAAc,oBAAI,IAAI;AAAA,EAC1B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,CAAC;AAGD,IAAM,kBAA4B;AAAA,EAChC;AAAA,EACA;AACF;AAUO,SAAS,yBAAyB,UAAqC,CAAC,GAAG;AAChF,QAAM,kBAAkB,CAAC,GAAG,iBAAiB,GAAI,QAAQ,WAAW,CAAC,CAAE;AAEvE,SAAO,SAAS,WAAW,SAAsB;AAC/C,UAAM,EAAE,SAAS,IAAI,QAAQ;AAG7B,QAAI,YAAY,IAAI,QAAQ,GAAG;AAC7B,aAAO,2BAAa,KAAK;AAAA,IAC3B;AAGA,QAAI,WAAW,UAAU,eAAe,GAAG;AACzC,aAAO,2BAAa,KAAK;AAAA,IAC3B;AAEA,UAAM,KAAK,QAAQ,QAAQ,IAAI,YAAY;AAC3C,UAAM,SAAS,QAAQ,QAAQ,IAAI,QAAQ;AAC3C,UAAM,YAAQ,yBAAY,IAAI,MAAM;AAGpC,QAAI,SAAS,SAAS,KAAK,GAAG;AAC5B,YAAM,eAAe,SAAS,MAAM,GAAG,EAAE,KAAK;AAC9C,YAAM,eAAe,IAAI,IAAI,4BAA4B,QAAQ,GAAG;AACpE,mBAAa,aAAa,IAAI,QAAQ,YAAY;AAClD,aAAO,cAAc,2BAAa,QAAQ,YAAY,CAAC;AAAA,IACzD;AAGA,QAAI,MAAM,SAAS;AACjB,YAAM,eAAe,IAAI,IAAI,4BAA4B,QAAQ,GAAG;AACpE,mBAAa,aAAa,IAAI,QAAQ,QAAQ;AAC9C,aAAO,cAAc,2BAAa,QAAQ,YAAY,CAAC;AAAA,IACzD;AAGA,UAAM,WAAW,2BAAa,KAAK;AACnC,aAAS,QAAQ,IAAI,QAAQ,oBAAoB;AACjD,WAAO;AAAA,EACT;AACF;AAMA,SAAS,cAAc,UAAsC;AAC3D,WAAS,QAAQ,IAAI,uBAAuB,QAAQ;AACpD,WAAS,QAAQ,IAAI,QAAQ,oBAAoB;AACjD,WAAS,QAAQ,IAAI,gBAAgB,KAAK;AAC1C,WAAS,QAAQ,OAAO,uBAAuB;AAC/C,SAAO;AACT;AAEA,SAAS,WAAW,MAAc,UAA6B;AAC7D,SAAO,SAAS,KAAK,CAAC,YAAY,UAAU,SAAS,IAAI,CAAC;AAC5D;AAEA,SAAS,UAAU,SAAiB,MAAuB;AACzD,QAAM,QAAQ,QACX,QAAQ,SAAS,gBAAgB,EACjC,QAAQ,OAAO,OAAO,EACtB,QAAQ,mBAAmB,IAAI;AAClC,SAAO,IAAI,OAAO,IAAI,KAAK,GAAG,EAAE,KAAK,IAAI;AAC3C;","names":[]}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { NextRequest, NextResponse } from 'next/server';
|
|
2
|
+
|
|
3
|
+
interface AgentSeoMiddlewareOptions {
|
|
4
|
+
/**
|
|
5
|
+
* Glob patterns for paths that should NEVER be rewritten to Markdown.
|
|
6
|
+
* Merged with built-in defaults (see `DEFAULT_EXCLUDE`).
|
|
7
|
+
*
|
|
8
|
+
* @example ['\/dashboard\/**', '\/admin\/**', '\/api\/private\/**']
|
|
9
|
+
*/
|
|
10
|
+
exclude?: string[];
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Creates a Next.js middleware that:
|
|
14
|
+
* 1. Detects AI bot requests via User-Agent
|
|
15
|
+
* 2. Rewrites AI bot requests to an internal transform API route
|
|
16
|
+
* that converts HTML → Markdown (runs on Node.js runtime)
|
|
17
|
+
* 3. Handles `.md` suffix requests (e.g. `/about.md` → transform `/about`)
|
|
18
|
+
* 4. Sets `Vary: Accept, User-Agent` on all responses
|
|
19
|
+
*/
|
|
20
|
+
declare function createAgentSeoMiddleware(options?: AgentSeoMiddlewareOptions): (request: NextRequest) => NextResponse<unknown>;
|
|
21
|
+
|
|
22
|
+
export { type AgentSeoMiddlewareOptions, createAgentSeoMiddleware };
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { NextRequest, NextResponse } from 'next/server';
|
|
2
|
+
|
|
3
|
+
interface AgentSeoMiddlewareOptions {
|
|
4
|
+
/**
|
|
5
|
+
* Glob patterns for paths that should NEVER be rewritten to Markdown.
|
|
6
|
+
* Merged with built-in defaults (see `DEFAULT_EXCLUDE`).
|
|
7
|
+
*
|
|
8
|
+
* @example ['\/dashboard\/**', '\/admin\/**', '\/api\/private\/**']
|
|
9
|
+
*/
|
|
10
|
+
exclude?: string[];
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Creates a Next.js middleware that:
|
|
14
|
+
* 1. Detects AI bot requests via User-Agent
|
|
15
|
+
* 2. Rewrites AI bot requests to an internal transform API route
|
|
16
|
+
* that converts HTML → Markdown (runs on Node.js runtime)
|
|
17
|
+
* 3. Handles `.md` suffix requests (e.g. `/about.md` → transform `/about`)
|
|
18
|
+
* 4. Sets `Vary: Accept, User-Agent` on all responses
|
|
19
|
+
*/
|
|
20
|
+
declare function createAgentSeoMiddleware(options?: AgentSeoMiddlewareOptions): (request: NextRequest) => NextResponse<unknown>;
|
|
21
|
+
|
|
22
|
+
export { type AgentSeoMiddlewareOptions, createAgentSeoMiddleware };
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
// src/middleware.ts
|
|
2
|
+
import { detectAgent } from "@agent-seo/core/edge";
|
|
3
|
+
import { NextResponse } from "next/server";
|
|
4
|
+
var ALWAYS_SKIP = /* @__PURE__ */ new Set([
|
|
5
|
+
"/favicon.ico",
|
|
6
|
+
"/robots.txt",
|
|
7
|
+
"/sitemap.xml",
|
|
8
|
+
"/llms.txt",
|
|
9
|
+
"/llms-full.txt"
|
|
10
|
+
]);
|
|
11
|
+
var DEFAULT_EXCLUDE = [
|
|
12
|
+
"/api/**",
|
|
13
|
+
"/_next/**"
|
|
14
|
+
];
|
|
15
|
+
function createAgentSeoMiddleware(options = {}) {
|
|
16
|
+
const excludePatterns = [...DEFAULT_EXCLUDE, ...options.exclude || []];
|
|
17
|
+
return function middleware(request) {
|
|
18
|
+
const { pathname } = request.nextUrl;
|
|
19
|
+
if (ALWAYS_SKIP.has(pathname)) {
|
|
20
|
+
return NextResponse.next();
|
|
21
|
+
}
|
|
22
|
+
if (isExcluded(pathname, excludePatterns)) {
|
|
23
|
+
return NextResponse.next();
|
|
24
|
+
}
|
|
25
|
+
const ua = request.headers.get("user-agent");
|
|
26
|
+
const accept = request.headers.get("accept");
|
|
27
|
+
const aiCtx = detectAgent(ua, accept);
|
|
28
|
+
if (pathname.endsWith(".md")) {
|
|
29
|
+
const originalPath = pathname.slice(0, -3) || "/";
|
|
30
|
+
const transformUrl = new URL("/api/agent-seo-transform", request.url);
|
|
31
|
+
transformUrl.searchParams.set("path", originalPath);
|
|
32
|
+
return setBotHeaders(NextResponse.rewrite(transformUrl));
|
|
33
|
+
}
|
|
34
|
+
if (aiCtx.isAIBot) {
|
|
35
|
+
const transformUrl = new URL("/api/agent-seo-transform", request.url);
|
|
36
|
+
transformUrl.searchParams.set("path", pathname);
|
|
37
|
+
return setBotHeaders(NextResponse.rewrite(transformUrl));
|
|
38
|
+
}
|
|
39
|
+
const response = NextResponse.next();
|
|
40
|
+
response.headers.set("Vary", "Accept, User-Agent");
|
|
41
|
+
return response;
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
function setBotHeaders(response) {
|
|
45
|
+
response.headers.set("Content-Disposition", "inline");
|
|
46
|
+
response.headers.set("Vary", "Accept, User-Agent");
|
|
47
|
+
response.headers.set("X-Robots-Tag", "all");
|
|
48
|
+
response.headers.delete("x-nextjs-matched-path");
|
|
49
|
+
return response;
|
|
50
|
+
}
|
|
51
|
+
function isExcluded(path, patterns) {
|
|
52
|
+
return patterns.some((pattern) => matchGlob(pattern, path));
|
|
53
|
+
}
|
|
54
|
+
function matchGlob(pattern, path) {
|
|
55
|
+
const regex = pattern.replace(/\*\*/g, "{{DOUBLESTAR}}").replace(/\*/g, "[^/]*").replace(/{{DOUBLESTAR}}/g, ".*");
|
|
56
|
+
return new RegExp(`^${regex}$`).test(path);
|
|
57
|
+
}
|
|
58
|
+
export {
|
|
59
|
+
createAgentSeoMiddleware
|
|
60
|
+
};
|
|
61
|
+
//# sourceMappingURL=middleware.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/middleware.ts"],"sourcesContent":["import { detectAgent } from '@agent-seo/core/edge';\nimport { NextResponse } from 'next/server';\nimport type { NextRequest } from 'next/server';\n\nexport interface AgentSeoMiddlewareOptions {\n /**\n * Glob patterns for paths that should NEVER be rewritten to Markdown.\n * Merged with built-in defaults (see `DEFAULT_EXCLUDE`).\n *\n * @example ['\\/dashboard\\/**', '\\/admin\\/**', '\\/api\\/private\\/**']\n */\n exclude?: string[];\n}\n\n/** Paths that are always skipped — framework internals + standard files. */\nconst ALWAYS_SKIP = new Set([\n '/favicon.ico',\n '/robots.txt',\n '/sitemap.xml',\n '/llms.txt',\n '/llms-full.txt',\n]);\n\n/** Default exclude patterns — users can extend but not remove these. */\nconst DEFAULT_EXCLUDE: string[] = [\n '/api/**',\n '/_next/**',\n];\n\n/**\n * Creates a Next.js middleware that:\n * 1. Detects AI bot requests via User-Agent\n * 2. Rewrites AI bot requests to an internal transform API route\n * that converts HTML → Markdown (runs on Node.js runtime)\n * 3. Handles `.md` suffix requests (e.g. `/about.md` → transform `/about`)\n * 4. Sets `Vary: Accept, User-Agent` on all responses\n */\nexport function createAgentSeoMiddleware(options: AgentSeoMiddlewareOptions = {}) {\n const excludePatterns = [...DEFAULT_EXCLUDE, ...(options.exclude || [])];\n\n return function middleware(request: NextRequest) {\n const { pathname } = request.nextUrl;\n\n // Skip standard files (robots.txt, favicon, llms.txt, etc.)\n if (ALWAYS_SKIP.has(pathname)) {\n return NextResponse.next();\n }\n\n // Skip excluded patterns (API routes, admin, dashboard, etc.)\n if (isExcluded(pathname, excludePatterns)) {\n return NextResponse.next();\n }\n\n const ua = request.headers.get('user-agent');\n const accept = request.headers.get('accept');\n const aiCtx = detectAgent(ua, accept);\n\n // Handle explicit .md suffix requests (e.g. /about.md)\n if (pathname.endsWith('.md')) {\n const originalPath = pathname.slice(0, -3) || '/';\n const transformUrl = new URL('/api/agent-seo-transform', request.url);\n transformUrl.searchParams.set('path', originalPath);\n return setBotHeaders(NextResponse.rewrite(transformUrl));\n }\n\n // If AI bot, rewrite to transform API\n if (aiCtx.isAIBot) {\n const transformUrl = new URL('/api/agent-seo-transform', request.url);\n transformUrl.searchParams.set('path', pathname);\n return setBotHeaders(NextResponse.rewrite(transformUrl));\n }\n\n // Normal request — just add Vary header\n const response = NextResponse.next();\n response.headers.set('Vary', 'Accept, User-Agent');\n return response;\n };\n}\n\n/**\n * Set clean, bot-friendly headers on a rewrite response.\n * Overrides Next.js RSC-related headers that can confuse AI bots.\n */\nfunction setBotHeaders(response: NextResponse): NextResponse {\n response.headers.set('Content-Disposition', 'inline');\n response.headers.set('Vary', 'Accept, User-Agent');\n response.headers.set('X-Robots-Tag', 'all');\n response.headers.delete('x-nextjs-matched-path');\n return response;\n}\n\nfunction isExcluded(path: string, patterns: string[]): boolean {\n return patterns.some((pattern) => matchGlob(pattern, path));\n}\n\nfunction matchGlob(pattern: string, path: string): boolean {\n const regex = pattern\n .replace(/\\*\\*/g, '{{DOUBLESTAR}}')\n .replace(/\\*/g, '[^/]*')\n .replace(/{{DOUBLESTAR}}/g, '.*');\n return new RegExp(`^${regex}$`).test(path);\n}\n"],"mappings":";AAAA,SAAS,mBAAmB;AAC5B,SAAS,oBAAoB;AAc7B,IAAM,cAAc,oBAAI,IAAI;AAAA,EAC1B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,CAAC;AAGD,IAAM,kBAA4B;AAAA,EAChC;AAAA,EACA;AACF;AAUO,SAAS,yBAAyB,UAAqC,CAAC,GAAG;AAChF,QAAM,kBAAkB,CAAC,GAAG,iBAAiB,GAAI,QAAQ,WAAW,CAAC,CAAE;AAEvE,SAAO,SAAS,WAAW,SAAsB;AAC/C,UAAM,EAAE,SAAS,IAAI,QAAQ;AAG7B,QAAI,YAAY,IAAI,QAAQ,GAAG;AAC7B,aAAO,aAAa,KAAK;AAAA,IAC3B;AAGA,QAAI,WAAW,UAAU,eAAe,GAAG;AACzC,aAAO,aAAa,KAAK;AAAA,IAC3B;AAEA,UAAM,KAAK,QAAQ,QAAQ,IAAI,YAAY;AAC3C,UAAM,SAAS,QAAQ,QAAQ,IAAI,QAAQ;AAC3C,UAAM,QAAQ,YAAY,IAAI,MAAM;AAGpC,QAAI,SAAS,SAAS,KAAK,GAAG;AAC5B,YAAM,eAAe,SAAS,MAAM,GAAG,EAAE,KAAK;AAC9C,YAAM,eAAe,IAAI,IAAI,4BAA4B,QAAQ,GAAG;AACpE,mBAAa,aAAa,IAAI,QAAQ,YAAY;AAClD,aAAO,cAAc,aAAa,QAAQ,YAAY,CAAC;AAAA,IACzD;AAGA,QAAI,MAAM,SAAS;AACjB,YAAM,eAAe,IAAI,IAAI,4BAA4B,QAAQ,GAAG;AACpE,mBAAa,aAAa,IAAI,QAAQ,QAAQ;AAC9C,aAAO,cAAc,aAAa,QAAQ,YAAY,CAAC;AAAA,IACzD;AAGA,UAAM,WAAW,aAAa,KAAK;AACnC,aAAS,QAAQ,IAAI,QAAQ,oBAAoB;AACjD,WAAO;AAAA,EACT;AACF;AAMA,SAAS,cAAc,UAAsC;AAC3D,WAAS,QAAQ,IAAI,uBAAuB,QAAQ;AACpD,WAAS,QAAQ,IAAI,QAAQ,oBAAoB;AACjD,WAAS,QAAQ,IAAI,gBAAgB,KAAK;AAC1C,WAAS,QAAQ,OAAO,uBAAuB;AAC/C,SAAO;AACT;AAEA,SAAS,WAAW,MAAc,UAA6B;AAC7D,SAAO,SAAS,KAAK,CAAC,YAAY,UAAU,SAAS,IAAI,CAAC;AAC5D;AAEA,SAAS,UAAU,SAAiB,MAAuB;AACzD,QAAM,QAAQ,QACX,QAAQ,SAAS,gBAAgB,EACjC,QAAQ,OAAO,OAAO,EACtB,QAAQ,mBAAmB,IAAI;AAClC,SAAO,IAAI,OAAO,IAAI,KAAK,GAAG,EAAE,KAAK,IAAI;AAC3C;","names":[]}
|
package/package.json
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@agent-seo/next",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Next.js plugin for AI-readable websites",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "https://github.com/pontiggia/agent-seo",
|
|
9
|
+
"directory": "packages/next"
|
|
10
|
+
},
|
|
11
|
+
"homepage": "https://github.com/pontiggia/agent-seo#readme",
|
|
12
|
+
"bugs": "https://github.com/pontiggia/agent-seo/issues",
|
|
13
|
+
"keywords": [
|
|
14
|
+
"seo",
|
|
15
|
+
"ai",
|
|
16
|
+
"llm",
|
|
17
|
+
"nextjs",
|
|
18
|
+
"next",
|
|
19
|
+
"middleware",
|
|
20
|
+
"llms-txt",
|
|
21
|
+
"agent",
|
|
22
|
+
"bot"
|
|
23
|
+
],
|
|
24
|
+
"type": "module",
|
|
25
|
+
"engines": {
|
|
26
|
+
"node": ">=20.0.0"
|
|
27
|
+
},
|
|
28
|
+
"main": "dist/index.cjs",
|
|
29
|
+
"module": "dist/index.js",
|
|
30
|
+
"types": "dist/index.d.ts",
|
|
31
|
+
"exports": {
|
|
32
|
+
".": {
|
|
33
|
+
"import": {
|
|
34
|
+
"types": "./dist/index.d.ts",
|
|
35
|
+
"default": "./dist/index.js"
|
|
36
|
+
},
|
|
37
|
+
"require": {
|
|
38
|
+
"types": "./dist/index.d.cts",
|
|
39
|
+
"default": "./dist/index.cjs"
|
|
40
|
+
}
|
|
41
|
+
},
|
|
42
|
+
"./middleware": {
|
|
43
|
+
"import": {
|
|
44
|
+
"types": "./dist/middleware.d.ts",
|
|
45
|
+
"default": "./dist/middleware.js"
|
|
46
|
+
},
|
|
47
|
+
"require": {
|
|
48
|
+
"types": "./dist/middleware.d.cts",
|
|
49
|
+
"default": "./dist/middleware.cjs"
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
},
|
|
53
|
+
"files": [
|
|
54
|
+
"dist"
|
|
55
|
+
],
|
|
56
|
+
"dependencies": {
|
|
57
|
+
"@agent-seo/core": "1.0.0"
|
|
58
|
+
},
|
|
59
|
+
"peerDependencies": {
|
|
60
|
+
"next": "^14.0.0 || ^15.0.0 || ^16.0.0"
|
|
61
|
+
},
|
|
62
|
+
"devDependencies": {
|
|
63
|
+
"next": "^16.0.0",
|
|
64
|
+
"react": "^19.0.0",
|
|
65
|
+
"react-dom": "^19.0.0"
|
|
66
|
+
},
|
|
67
|
+
"scripts": {
|
|
68
|
+
"build": "tsup",
|
|
69
|
+
"test": "vitest run",
|
|
70
|
+
"typecheck": "tsc --noEmit"
|
|
71
|
+
}
|
|
72
|
+
}
|