@webnumseoagent/next 0.1.21 → 0.1.23
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/client.ts +34 -0
- package/dist/client.d.ts +1 -0
- package/dist/client.js +36 -0
- package/package.json +1 -1
package/client.ts
CHANGED
|
@@ -38,6 +38,7 @@ interface SeoFields {
|
|
|
38
38
|
siteName?: string;
|
|
39
39
|
baseUrl?: string;
|
|
40
40
|
orgProfile?: { name?: string };
|
|
41
|
+
indexNowKey?: string; // ключ IndexNow (см. seoIndexNowKey / файл /seoagent-indexnow.txt)
|
|
41
42
|
}
|
|
42
43
|
|
|
43
44
|
// Подстановка переменных шаблона (RankMath-стиль). Значения подставляем ЧЕРЕЗ функции — иначе `$`
|
|
@@ -158,6 +159,15 @@ function mergeIntoMetadata(fallback: Metadata, f: SeoFields, route: string): Met
|
|
|
158
159
|
m.description = f.description;
|
|
159
160
|
}
|
|
160
161
|
if (f.canonical) m.alternates = { ...(m.alternates ?? {}), canonical: f.canonical };
|
|
162
|
+
// Авто self-canonical: если ни сайт (fallback), ни конфиг не задали canonical — ставим ссылку на
|
|
163
|
+
// саму страницу. Существующий canonical НЕ трогаем (сайт может каноникалить на другой URL — дедуп).
|
|
164
|
+
// Путь нормализуем к ведущему «/»; при заданном baseUrl делаем абсолютным, иначе относительный —
|
|
165
|
+
// он тоже даёт верный self-canonical (резолвится к URL страницы / через metadataBase сайта).
|
|
166
|
+
if (!m.alternates?.canonical) {
|
|
167
|
+
const path = "/" + String(route ?? "").replace(/^\/+/, "");
|
|
168
|
+
const self = f.baseUrl ? f.baseUrl.replace(/\/+$/, "") + path : path;
|
|
169
|
+
m.alternates = { ...(m.alternates ?? {}), canonical: self };
|
|
170
|
+
}
|
|
161
171
|
if (f.robots) {
|
|
162
172
|
const r = f.robots;
|
|
163
173
|
const base: Record<string, unknown> =
|
|
@@ -296,6 +306,30 @@ export async function seoJsonLd(route: string, locale?: string): Promise<unknown
|
|
|
296
306
|
return data?.fields?.jsonLd ?? [];
|
|
297
307
|
}
|
|
298
308
|
|
|
309
|
+
// Ключ IndexNow для файла-подтверждения. Смонтируй на сайте:
|
|
310
|
+
// // app/seoagent-indexnow.txt/route.ts
|
|
311
|
+
// import { seoIndexNowKey } from "@webnumseoagent/next";
|
|
312
|
+
// export const dynamic = "force-dynamic";
|
|
313
|
+
// export async function GET() {
|
|
314
|
+
// return new Response(await seoIndexNowKey(), { headers: { "content-type": "text/plain; charset=utf-8" } });
|
|
315
|
+
// }
|
|
316
|
+
// Тянем СВЕЖИМ (no-store): ключ стабилен, но так доступен сразу после генерации платформой.
|
|
317
|
+
export async function seoIndexNowKey(): Promise<string> {
|
|
318
|
+
if (!API || !SITE || !TOKEN) return "";
|
|
319
|
+
try {
|
|
320
|
+
const r = await fetch(`${API}/api/seo/${SITE}/config?route=%2F&locale=`, {
|
|
321
|
+
headers: { "x-seo-agent-key": TOKEN },
|
|
322
|
+
cache: "no-store",
|
|
323
|
+
signal: AbortSignal.timeout(5000),
|
|
324
|
+
});
|
|
325
|
+
if (!r.ok) return "";
|
|
326
|
+
const data = (await r.json()) as { fields?: SeoFields };
|
|
327
|
+
return data.fields?.indexNowKey ?? "";
|
|
328
|
+
} catch {
|
|
329
|
+
return "";
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
|
|
299
333
|
// Счётчики аналитики (общесайтовые). Читаются в analytics.tsx (<SeoAnalytics/>).
|
|
300
334
|
export async function seoAnalytics(route = "/", locale?: string): Promise<{ ga4?: string; gtm?: string; metrica?: string }> {
|
|
301
335
|
const data = await fetchJson<{ fields: SeoFields }>(
|
package/dist/client.d.ts
CHANGED
|
@@ -11,6 +11,7 @@ export declare function seoSitemap(): Promise<MetadataRoute.Sitemap>;
|
|
|
11
11
|
export declare function seoRobots(): Promise<MetadataRoute.Robots>;
|
|
12
12
|
export declare function seoRobotsTxt(): Promise<string>;
|
|
13
13
|
export declare function seoJsonLd(route: string, locale?: string): Promise<unknown[]>;
|
|
14
|
+
export declare function seoIndexNowKey(): Promise<string>;
|
|
14
15
|
export declare function seoAnalytics(route?: string, locale?: string): Promise<{
|
|
15
16
|
ga4?: string;
|
|
16
17
|
gtm?: string;
|
package/dist/client.js
CHANGED
|
@@ -18,6 +18,7 @@ exports.seoSitemap = seoSitemap;
|
|
|
18
18
|
exports.seoRobots = seoRobots;
|
|
19
19
|
exports.seoRobotsTxt = seoRobotsTxt;
|
|
20
20
|
exports.seoJsonLd = seoJsonLd;
|
|
21
|
+
exports.seoIndexNowKey = seoIndexNowKey;
|
|
21
22
|
exports.seoAnalytics = seoAnalytics;
|
|
22
23
|
const API = process.env.SEOAGENT_API_BASE ?? "";
|
|
23
24
|
const SITE = process.env.SEOAGENT_SITE_ID ?? "";
|
|
@@ -157,6 +158,15 @@ function mergeIntoMetadata(fallback, f, route) {
|
|
|
157
158
|
}
|
|
158
159
|
if (f.canonical)
|
|
159
160
|
m.alternates = { ...(m.alternates ?? {}), canonical: f.canonical };
|
|
161
|
+
// Авто self-canonical: если ни сайт (fallback), ни конфиг не задали canonical — ставим ссылку на
|
|
162
|
+
// саму страницу. Существующий canonical НЕ трогаем (сайт может каноникалить на другой URL — дедуп).
|
|
163
|
+
// Путь нормализуем к ведущему «/»; при заданном baseUrl делаем абсолютным, иначе относительный —
|
|
164
|
+
// он тоже даёт верный self-canonical (резолвится к URL страницы / через metadataBase сайта).
|
|
165
|
+
if (!m.alternates?.canonical) {
|
|
166
|
+
const path = "/" + String(route ?? "").replace(/^\/+/, "");
|
|
167
|
+
const self = f.baseUrl ? f.baseUrl.replace(/\/+$/, "") + path : path;
|
|
168
|
+
m.alternates = { ...(m.alternates ?? {}), canonical: self };
|
|
169
|
+
}
|
|
160
170
|
if (f.robots) {
|
|
161
171
|
const r = f.robots;
|
|
162
172
|
const base = typeof m.robots === "string" ? robotsFromString(m.robots)
|
|
@@ -294,6 +304,32 @@ async function seoJsonLd(route, locale) {
|
|
|
294
304
|
const data = await fetchJson(`/config?route=${encodeURIComponent(route)}&locale=${encodeURIComponent(locale ?? "")}`);
|
|
295
305
|
return data?.fields?.jsonLd ?? [];
|
|
296
306
|
}
|
|
307
|
+
// Ключ IndexNow для файла-подтверждения. Смонтируй на сайте:
|
|
308
|
+
// // app/seoagent-indexnow.txt/route.ts
|
|
309
|
+
// import { seoIndexNowKey } from "@webnumseoagent/next";
|
|
310
|
+
// export const dynamic = "force-dynamic";
|
|
311
|
+
// export async function GET() {
|
|
312
|
+
// return new Response(await seoIndexNowKey(), { headers: { "content-type": "text/plain; charset=utf-8" } });
|
|
313
|
+
// }
|
|
314
|
+
// Тянем СВЕЖИМ (no-store): ключ стабилен, но так доступен сразу после генерации платформой.
|
|
315
|
+
async function seoIndexNowKey() {
|
|
316
|
+
if (!API || !SITE || !TOKEN)
|
|
317
|
+
return "";
|
|
318
|
+
try {
|
|
319
|
+
const r = await fetch(`${API}/api/seo/${SITE}/config?route=%2F&locale=`, {
|
|
320
|
+
headers: { "x-seo-agent-key": TOKEN },
|
|
321
|
+
cache: "no-store",
|
|
322
|
+
signal: AbortSignal.timeout(5000),
|
|
323
|
+
});
|
|
324
|
+
if (!r.ok)
|
|
325
|
+
return "";
|
|
326
|
+
const data = (await r.json());
|
|
327
|
+
return data.fields?.indexNowKey ?? "";
|
|
328
|
+
}
|
|
329
|
+
catch {
|
|
330
|
+
return "";
|
|
331
|
+
}
|
|
332
|
+
}
|
|
297
333
|
// Счётчики аналитики (общесайтовые). Читаются в analytics.tsx (<SeoAnalytics/>).
|
|
298
334
|
async function seoAnalytics(route = "/", locale) {
|
|
299
335
|
const data = await fetchJson(`/config?route=${encodeURIComponent(route)}&locale=${encodeURIComponent(locale ?? "")}`);
|
package/package.json
CHANGED