@webnumseoagent/next 0.1.12 → 0.1.14
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/bin/cli.mjs +21 -0
- package/client.ts +6 -0
- package/dist/client.js +5 -0
- package/package.json +1 -1
package/bin/cli.mjs
CHANGED
|
@@ -262,6 +262,27 @@ async function init() {
|
|
|
262
262
|
}
|
|
263
263
|
}
|
|
264
264
|
|
|
265
|
+
// 4.6 Мониторинг 404: роут-приёмник на сайте (форвардит хит в платформу с СЕРВЕРНЫМ
|
|
266
|
+
// токеном) + not-found-репортер (клиентский маячок с путём 404). Репортер отдаём сырым
|
|
267
|
+
// исходником (client-компонент из dist пакета не работает — tsc ставит "use strict" первой).
|
|
268
|
+
{
|
|
269
|
+
const reqType = ext === "ts" ? ": Request" : "";
|
|
270
|
+
const fwd = `export async function POST(req${reqType}) {\n const origin = req.headers.get("origin"), host = req.headers.get("host");\n if (origin && host) { try { if (new URL(origin).host !== host) return new Response(null, { status: 204 }); } catch { return new Response(null, { status: 204 }); } }\n const API = process.env.SEOAGENT_API_BASE, SITE = process.env.SEOAGENT_SITE_ID, TOKEN = process.env.SEOAGENT_TOKEN;\n if (!API || !SITE || !TOKEN) return new Response(null, { status: 204 });\n let path = "";\n try { const b = await req.json(); if (typeof b?.path === "string") path = b.path; } catch {}\n if (!path) return new Response(null, { status: 204 });\n try { await fetch(\`\${API}/api/seo/\${SITE}/not-found\`, { method: "POST", headers: { "content-type": "application/json", "x-seo-agent-key": TOKEN }, body: JSON.stringify({ path }), signal: AbortSignal.timeout(5000) }); } catch {}\n return new Response(null, { status: 204 });\n}\n`;
|
|
271
|
+
await ensureFile(path.join(root, appDir, `api/seoagent-404/route.${ext}`), fwd);
|
|
272
|
+
|
|
273
|
+
const reporter = `"use client";\n\nimport Link from "next/link";\nimport { usePathname } from "next/navigation";\nimport { useEffect, useRef } from "react";\n\n// Страница 404 + репортер для «Мониторинг 404» (SEO Agent).\nexport default function NotFound() {\n const pathname = usePathname();\n const lastSent = useRef(null);\n useEffect(() => {\n const p = pathname || window.location.pathname;\n if (lastSent.current === p) return;\n lastSent.current = p;\n try {\n const body = JSON.stringify({ path: p });\n if (navigator.sendBeacon) navigator.sendBeacon("/api/seoagent-404", new Blob([body], { type: "application/json" }));\n else fetch("/api/seoagent-404", { method: "POST", headers: { "content-type": "application/json" }, body, keepalive: true }).catch(() => {});\n } catch {}\n }, [pathname]);\n return (\n <main style={{ minHeight: "60vh", display: "flex", flexDirection: "column", alignItems: "center", justifyContent: "center", gap: "1rem", padding: "2rem", textAlign: "center" }}>\n <h1 style={{ fontSize: "3rem", margin: 0 }}>404</h1>\n <p style={{ color: "#64748b", margin: 0 }}>Страница не найдена.</p>\n <Link href="/" style={{ color: "#2563eb" }}>На главную</Link>\n </main>\n );\n}\n`;
|
|
274
|
+
const nfExt = ext === "ts" ? "tsx" : "js";
|
|
275
|
+
const hasNotFound = await anyExists([
|
|
276
|
+
`${appDir}/not-found.js`, `${appDir}/not-found.jsx`, `${appDir}/not-found.ts`, `${appDir}/not-found.tsx`,
|
|
277
|
+
]);
|
|
278
|
+
if (hasNotFound) {
|
|
279
|
+
log(`\n \x1b[1mnot-found уже есть\x1b[0m — добавь репортер 404 (Мониторинг 404) в свой not-found:`);
|
|
280
|
+
log(` \x1b[36m"use client"; ... useEffect(() => { navigator.sendBeacon?.("/api/seoagent-404", new Blob([JSON.stringify({ path: location.pathname })], { type: "application/json" })); }, []);\x1b[0m`);
|
|
281
|
+
} else {
|
|
282
|
+
await ensureFile(path.join(root, appDir, `not-found.${nfExt}`), reporter);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
265
286
|
// 5. .env.local
|
|
266
287
|
const envPath = path.join(root, ".env.local");
|
|
267
288
|
const cur = (await exists(envPath)) ? await fs.readFile(envPath, "utf8") : "";
|
package/client.ts
CHANGED
|
@@ -24,6 +24,7 @@ interface SeoFields {
|
|
|
24
24
|
og?: { title?: string; description?: string; image?: string };
|
|
25
25
|
twitter?: { card?: string; image?: string };
|
|
26
26
|
jsonLd?: unknown[];
|
|
27
|
+
verification?: Record<string, string>; // проверочные мета-теги: metaName → content
|
|
27
28
|
}
|
|
28
29
|
|
|
29
30
|
async function fetchJson<T>(path: string, revalidate = 300): Promise<T | null> {
|
|
@@ -75,6 +76,11 @@ function mergeIntoMetadata(fallback: Metadata, f: SeoFields): Metadata {
|
|
|
75
76
|
...(f.twitter.image ? { images: [f.twitter.image] } : {}),
|
|
76
77
|
};
|
|
77
78
|
}
|
|
79
|
+
// Проверочные мета-теги веб-мастерских → <meta name content> через metadata.other.
|
|
80
|
+
// Мержим, не затирая существующий other сайта.
|
|
81
|
+
if (f.verification && typeof f.verification === "object") {
|
|
82
|
+
m.other = { ...(m.other ?? {}), ...f.verification };
|
|
83
|
+
}
|
|
78
84
|
return m;
|
|
79
85
|
}
|
|
80
86
|
|
package/dist/client.js
CHANGED
|
@@ -75,6 +75,11 @@ function mergeIntoMetadata(fallback, f) {
|
|
|
75
75
|
...(f.twitter.image ? { images: [f.twitter.image] } : {}),
|
|
76
76
|
};
|
|
77
77
|
}
|
|
78
|
+
// Проверочные мета-теги веб-мастерских → <meta name content> через metadata.other.
|
|
79
|
+
// Мержим, не затирая существующий other сайта.
|
|
80
|
+
if (f.verification && typeof f.verification === "object") {
|
|
81
|
+
m.other = { ...(m.other ?? {}), ...f.verification };
|
|
82
|
+
}
|
|
78
83
|
return m;
|
|
79
84
|
}
|
|
80
85
|
// В generateMetadata: const md = await seoMeta({ route, locale, fallback: compiled });
|
package/package.json
CHANGED