@djangocfg/seo 2.1.50 → 2.1.52
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/dist/cli.mjs +82 -5
- package/dist/cli.mjs.map +1 -1
- package/dist/crawler/index.mjs +16 -0
- package/dist/crawler/index.mjs.map +1 -1
- package/dist/index.mjs +75 -0
- package/dist/index.mjs.map +1 -1
- package/dist/reports/index.mjs +59 -0
- package/dist/reports/index.mjs.map +1 -1
- package/dist/routes/index.mjs +7 -5
- package/dist/routes/index.mjs.map +1 -1
- package/package.json +2 -2
- package/src/crawler/robots-parser.ts +26 -0
- package/src/reports/claude-context.ts +61 -0
- package/src/routes/analyzer.ts +17 -10
package/dist/cli.mjs
CHANGED
|
@@ -1003,6 +1003,22 @@ async function analyzeRobotsTxt(siteUrl) {
|
|
|
1003
1003
|
}
|
|
1004
1004
|
analysis.exists = true;
|
|
1005
1005
|
analysis.content = await response.text();
|
|
1006
|
+
if (analysis.content.includes("content-signal") || analysis.content.includes("Content-Signal") || analysis.content.includes("ai-input") || analysis.content.includes("ai-train")) {
|
|
1007
|
+
analysis.issues.push({
|
|
1008
|
+
id: "cloudflare-managed-robots",
|
|
1009
|
+
url: robotsUrl,
|
|
1010
|
+
category: "technical",
|
|
1011
|
+
severity: "warning",
|
|
1012
|
+
title: "Cloudflare managed robots.txt detected",
|
|
1013
|
+
description: `Your robots.txt is being overwritten by Cloudflare's "Content Signals Policy". Your app/robots.ts file is not being served.`,
|
|
1014
|
+
recommendation: 'Disable in Cloudflare Dashboard: Security \u2192 Settings \u2192 "Manage your robots.txt" \u2192 Set to "Off".',
|
|
1015
|
+
detectedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1016
|
+
metadata: {
|
|
1017
|
+
cloudflareFeature: "Managed robots.txt",
|
|
1018
|
+
docsUrl: "https://developers.cloudflare.com/bots/additional-configurations/managed-robots-txt/"
|
|
1019
|
+
}
|
|
1020
|
+
});
|
|
1021
|
+
}
|
|
1006
1022
|
const robots = robotsParser(robotsUrl, analysis.content);
|
|
1007
1023
|
analysis.sitemaps = robots.getSitemaps();
|
|
1008
1024
|
if (analysis.sitemaps.length === 0) {
|
|
@@ -2097,6 +2113,65 @@ function generateClaudeContext(report) {
|
|
|
2097
2113
|
lines.push("- Parallel `@folder` - skipped");
|
|
2098
2114
|
lines.push("- Private `_folder` - skipped");
|
|
2099
2115
|
lines.push("");
|
|
2116
|
+
lines.push("## SEO Files (Next.js App Router)");
|
|
2117
|
+
lines.push("");
|
|
2118
|
+
lines.push("**Required files in `app/`:**");
|
|
2119
|
+
lines.push("");
|
|
2120
|
+
lines.push("### sitemap.xml/route.ts");
|
|
2121
|
+
lines.push("```typescript");
|
|
2122
|
+
lines.push("import { createSitemapHandler } from '@djangocfg/nextjs/sitemap';");
|
|
2123
|
+
lines.push("");
|
|
2124
|
+
lines.push('export const dynamic = "force-static";');
|
|
2125
|
+
lines.push("export const { GET } = createSitemapHandler({");
|
|
2126
|
+
lines.push(" siteUrl,");
|
|
2127
|
+
lines.push(" staticPages: [");
|
|
2128
|
+
lines.push(' { loc: "/", priority: 1.0, changefreq: "daily" },');
|
|
2129
|
+
lines.push(' { loc: "/about", priority: 0.8 },');
|
|
2130
|
+
lines.push(" ],");
|
|
2131
|
+
lines.push(" dynamicPages: async () => fetchPagesFromAPI(),");
|
|
2132
|
+
lines.push("});");
|
|
2133
|
+
lines.push("```");
|
|
2134
|
+
lines.push("");
|
|
2135
|
+
lines.push("### robots.ts");
|
|
2136
|
+
lines.push("```typescript");
|
|
2137
|
+
lines.push("import type { MetadataRoute } from 'next';");
|
|
2138
|
+
lines.push("");
|
|
2139
|
+
lines.push("export default function robots(): MetadataRoute.Robots {");
|
|
2140
|
+
lines.push(" return {");
|
|
2141
|
+
lines.push(' rules: { userAgent: "*", allow: "/" },');
|
|
2142
|
+
lines.push(" sitemap: `${siteUrl}/sitemap.xml`,");
|
|
2143
|
+
lines.push(" };");
|
|
2144
|
+
lines.push("}");
|
|
2145
|
+
lines.push("```");
|
|
2146
|
+
lines.push("");
|
|
2147
|
+
lines.push("### Cloudflare Override");
|
|
2148
|
+
lines.push("");
|
|
2149
|
+
lines.push('If robots.txt shows "Content-Signal" or "ai-train" \u2014 Cloudflare is overriding your file.');
|
|
2150
|
+
lines.push('**Fix:** Dashboard \u2192 Security \u2192 Settings \u2192 "Manage your robots.txt" \u2192 Set to "Off"');
|
|
2151
|
+
lines.push("");
|
|
2152
|
+
lines.push("### Declarative Routes with SEO");
|
|
2153
|
+
lines.push("");
|
|
2154
|
+
lines.push("Create `app/_routes/` with SEO metadata for sitemap:");
|
|
2155
|
+
lines.push("```typescript");
|
|
2156
|
+
lines.push("import { defineRoute } from '@djangocfg/nextjs/navigation';");
|
|
2157
|
+
lines.push("");
|
|
2158
|
+
lines.push("export const home = defineRoute('/', {");
|
|
2159
|
+
lines.push(" label: 'Home',");
|
|
2160
|
+
lines.push(" protected: false,");
|
|
2161
|
+
lines.push(" priority: 1.0, // Sitemap priority 0.0-1.0");
|
|
2162
|
+
lines.push(" changefreq: 'daily', // always|hourly|daily|weekly|monthly|yearly|never");
|
|
2163
|
+
lines.push(" noindex: false, // Exclude from sitemap");
|
|
2164
|
+
lines.push("});");
|
|
2165
|
+
lines.push("");
|
|
2166
|
+
lines.push("export const staticRoutes = [home, about, contact];");
|
|
2167
|
+
lines.push("```");
|
|
2168
|
+
lines.push("");
|
|
2169
|
+
lines.push("Then in `sitemap.xml/route.ts`:");
|
|
2170
|
+
lines.push("```typescript");
|
|
2171
|
+
lines.push("import { routes } from '@/app/_routes';");
|
|
2172
|
+
lines.push("routes.getAllStaticRoutes().filter(r => !r.metadata.noindex)");
|
|
2173
|
+
lines.push("```");
|
|
2174
|
+
lines.push("");
|
|
2100
2175
|
lines.push("## Link Guidelines");
|
|
2101
2176
|
lines.push("");
|
|
2102
2177
|
lines.push("### Nextra/MDX Projects (content/)");
|
|
@@ -2414,28 +2489,29 @@ function createRouteInfo(path6, filePath, type) {
|
|
|
2414
2489
|
function compareWithSitemap(scanResult, sitemapUrls, baseUrl) {
|
|
2415
2490
|
const appRoutes = scanResult.routes.filter((r) => r.type === "page");
|
|
2416
2491
|
const sitemapPaths = new Set(
|
|
2417
|
-
sitemapUrls.map((url) => {
|
|
2492
|
+
sitemapUrls.filter((url) => url).map((url) => {
|
|
2418
2493
|
try {
|
|
2419
2494
|
return new URL(url).pathname;
|
|
2420
2495
|
} catch {
|
|
2421
2496
|
return url;
|
|
2422
2497
|
}
|
|
2423
|
-
})
|
|
2498
|
+
}).filter(Boolean)
|
|
2424
2499
|
);
|
|
2425
2500
|
const missingFromSitemap = [];
|
|
2426
2501
|
const matching = [];
|
|
2427
2502
|
for (const route of scanResult.staticRoutes) {
|
|
2428
|
-
const path6 = route.path
|
|
2503
|
+
const path6 = route.path || "/";
|
|
2429
2504
|
if (sitemapPaths.has(path6) || sitemapPaths.has(path6 + "/") || sitemapPaths.has(path6.replace(/\/$/, ""))) {
|
|
2430
2505
|
matching.push(route);
|
|
2431
2506
|
} else {
|
|
2432
2507
|
missingFromSitemap.push(route);
|
|
2433
2508
|
}
|
|
2434
2509
|
}
|
|
2435
|
-
const staticPaths = new Set(scanResult.staticRoutes.map((r) => r.path));
|
|
2436
|
-
const dynamicPatterns = scanResult.dynamicRoutes.map((r) => routeToRegex(r.path));
|
|
2510
|
+
const staticPaths = new Set(scanResult.staticRoutes.map((r) => r.path).filter(Boolean));
|
|
2511
|
+
const dynamicPatterns = scanResult.dynamicRoutes.filter((r) => r.path).map((r) => routeToRegex(r.path));
|
|
2437
2512
|
const extraInSitemap = [];
|
|
2438
2513
|
for (const path6 of sitemapPaths) {
|
|
2514
|
+
if (!path6) continue;
|
|
2439
2515
|
if (staticPaths.has(path6) || staticPaths.has(path6 + "/") || staticPaths.has(path6.replace(/\/$/, ""))) {
|
|
2440
2516
|
continue;
|
|
2441
2517
|
}
|
|
@@ -2452,6 +2528,7 @@ function compareWithSitemap(scanResult, sitemapUrls, baseUrl) {
|
|
|
2452
2528
|
};
|
|
2453
2529
|
}
|
|
2454
2530
|
function routeToRegex(routePath) {
|
|
2531
|
+
if (!routePath) return /^$/;
|
|
2455
2532
|
let pattern = routePath.replace(/[.+?^${}()|\\]/g, "\\$&").replace(/\[\[\.\.\.([^\]]+)\]\]/g, "(?:/.*)?").replace(/\[\.\.\.([^\]]+)\]/g, "/.+").replace(/\[([^\]]+)\]/g, "/[^/]+");
|
|
2456
2533
|
pattern = `^${pattern}/?$`;
|
|
2457
2534
|
return new RegExp(pattern);
|